/*
 * Copyright 2011 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

define([], function() {
/*
 * Copyright 2011, 2012, 2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Support IE and Node constant
 */
 
try {
    if (Node.ELEMENT_NODE != 1) {
        throw true;
    }
}
catch(e) {
    var Node = {
        ELEMENT_NODE:                1,
        ATTRIBUTE_NODE:              2,
        TEXT_NODE:                   3,
        CDATA_SECTION_NODE:          4,
        ENTITY_REFERENCE_NODE:       5,
        ENTITY_NODE:                 6,
        PROCESSING_INSTRUCTION_NODE: 7,
        COMMENT_NODE:                8,
        DOCUMENT_NODE:               9,
        DOCUMENT_TYPE_NODE:         10,
        DOCUMENT_FRAGMENT_NODE:     11,
        NOTATION_NODE:              12
    };
}

/* ---------------------------------------------------------------- */
/*                       OpenAjax Constants                         */ 
/* ---------------------------------------------------------------- */


/** 
 * @namespace OpenAjax
 */

var OpenAjax = OpenAjax || {};
 
/** 
 * @namespace OpenAjax.a11y
 */

OpenAjax.a11y = OpenAjax.a11y || {};
OpenAjax.a11y.VERSION = "0.9.1";

/**
 * @method getVersion
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Get the current version of the evaluation library, rules and rulesets
 *
 * @return  {String}  Returns the version number of the evaluation library
 */

OpenAjax.a11y.getVersion = function () {
  return this.VERSION;
};

/** 
 * @namespace OpenAjax.a11y.cache
 */

OpenAjax.a11y.cache = OpenAjax.a11y.cache || {};

/** 
 * @namespace OpenAjax.a11y.nls
 */

OpenAjax.a11y.nls = OpenAjax.a11y.nls || {};

/** 
 * @constant EVENT_HANDLER_PROCESSOR
 * @memberOf OpenAjax.a11y
 * @type String
 * @default 'none'
 * @desc Defines an event handler enumeration method
 *       Since there is no standard way to enumerate event handlers
 *       need to use proprietary methods to get event information.
 *
 *       Current support:
 *       'firefox'  : uses Firefox (Mozilla) component technology to get 
 *                    event information
 *       'fae-util' : uses HTMLUnit features of fae-util to find event 
 *                    information
 *       'none'     : disables gathering of event information and 
 *                    therefore event related rules are not analyzed
 */
OpenAjax.a11y.EVENT_HANDLER_PROCESSOR = "none";

/**
 * @constant URL_TESTING_ENABLED
 *
 * @memberOf OpenAjax.a11y
 *
 * @type Boolean
 * @default false
 * @desc Enable or disable testing of broken links
 *       the default should be false, due to performance issues
 *       of testing links
 */
OpenAjax.a11y.URL_TESTING_ENABLED  = false;  

/**
 * @constant SUPPORTS_URL_TESTING
 *
 * @memberOf OpenAjax.a11y
 *
 * @type Boolean
 * @default false
 * @desc If true the analysis engine supports URL testing
 */
OpenAjax.a11y.SUPPORTS_URL_TESTING = true;

/**
 * @constant DATA_TABLE_ASSUMPTION
 *
 * @memberOf OpenAjax.a11y
 *
 * @type Boolean
 * @default true
 * @desc If true assume table markup is for a data table
 *       If false assume table markup is for layout, unless header cells or other 
 *       information indciates its a data table
 */
OpenAjax.a11y.DATA_TABLE_ASSUMPTION  = true;  


/**
 * @constant ELEMENT_FORMATING
 * @memberOf OpenAjax.a11y
 * @type String
 * @default 'CAPS'
 * @desc Defines the formating of element names in NLS message strings
 */
OpenAjax.a11y.ELEMENT_FORMATING = 'CAPS';


/**
 * @constant RESULT_FILTER
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants related to filtering both node results and rule results
 * @example
 * OpenAjax.a11y.RESULT_FILTER.ALL
 * OpenAjax.a11y.RESULT_FILTER.PASS
 * OpenAjax.a11y.RESULT_FILTER.VIOLATION
 * OpenAjax.a11y.RESULT_FILTER.WARNING
 * OpenAjax.a11y.RESULT_FILTER.PAGE_MANUAL_CHECK
 * OpenAjax.a11y.RESULT_FILTER.ELEMENT_MANUAL_CHECK
 * OpenAjax.a11y.RESULT_FILTER.HIDDEN
 * OpenAjax.a11y.RESULT_FILTER.NA
 */ 
OpenAjax.a11y.RESULT_FILTER = OpenAjax.a11y.RESULT_FILTER || {
  PASS                 : 0x0001,
  VIOLATION            : 0x0002,
  WARNING              : 0x0004,
  PAGE_MANUAL_CHECK    : 0x0008,
  ELEMENT_MANUAL_CHECK : 0x0010,
  MANUAL_CHECK         : 0x0018,
  HIDDEN               : 0x0020, // hidden only applies to node results 
  NOT_APPLICABLE       : 0x0040,  // not applicable only applies to rule results
  PAGE                 : 0x0080,  
  ALL                  : 0x00FF
};

/**
 * @constant DEFAULT_PREFS
 * @memberOf OpenAjax.a11y
 * @type Object
 * @desc Default setting for consumers of the OpenAjax cache 
 */

OpenAjax.a11y.DEFAULT_PREFS = OpenAjax.a11y.DEFAULT_PREFS || {
  RULESET_ID     : "WCAG20_ARIA_TRANS",
  WCAG20_LEVEL   : 3,
  BROKEN_LINKS   : false
};


/**
 * @constant WCAG20_PRINCIPLE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Numercial constant representing a WCAG 2.0 Principles
 *
 * @example
 * OpenAjax.a11y.WCAG20_PRINCIPLE.P_1  
 * OpenAjax.a11y.WCAG20_PRINCIPLE.P_2  
 * OpenAjax.a11y.WCAG20_PRINCIPLE.P_3  
 * OpenAjax.a11y.WCAG20_PRINCIPLE.P_4  
 */
OpenAjax.a11y.WCAG20_PRINCIPLE = OpenAjax.a11y.WCAG20_PRINCIPLE || {
  P_1          : 0x000001,
  P_2          : 0x000002,
  P_3          : 0x000004,
  P_4          : 0x000008,
  ALL          : 0x00000F
};

/**
 * @constant WCAG20_GUIDELINE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Numercial constant representing a WCAG 2.0 Guidelines
 *
 * @example
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_1_1  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_1_2  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_1_3  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_1_4  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_2_1  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_2_2  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_2_3  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_2_4  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_3_1  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_3_2  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_3_3  
 * OpenAjax.a11y.WCAG20_GUIDELINE.G_4_1  
 */
OpenAjax.a11y.WCAG20_GUIDELINE = OpenAjax.a11y.WCAG20_GUIDELINE || {
  G_1_1          : 0x000010,
  G_1_2          : 0x000020,
  G_1_3          : 0x000040,
  G_1_4          : 0x000080,
  G_2_1          : 0x000100,
  G_2_2          : 0x000200,
  G_2_3          : 0x000400,
  G_2_4          : 0x000800,
  G_3_1          : 0x001000,
  G_3_2          : 0x002000,
  G_3_3          : 0x004000,
  G_4_1          : 0x010000,
  ALL            : 0x00FFF0
};

/**
 * @constant WCAG20_SUCCESS_CRITERION
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Numercial constant representing a WCAG 2.0 Success Criteria
 *
 * @example
 * OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_1_1  
 * ....
 * OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_4_1_2  
 */
OpenAjax.a11y.WCAG20_SUCCESS_CRITERION = OpenAjax.a11y.WCAG20_SUCCESS_CRITERION || {
  SC_1_1_1          : 1101,
  SC_1_2_1          : 1201,
  SC_1_2_2          : 1202,
  SC_1_2_3          : 1203,
  SC_1_2_4          : 1204,
  SC_1_2_5          : 1205,
  SC_1_2_6          : 1206,
  SC_1_2_7          : 1207,
  SC_1_2_8          : 1208,
  SC_1_2_9          : 1209,
  SC_1_3_1          : 1301,
  SC_1_3_2          : 1302,
  SC_1_3_3          : 1303,
  SC_1_4_1          : 1401,
  SC_1_4_2          : 1402,
  SC_1_4_3          : 1403,
  SC_1_4_4          : 1404,
  SC_1_4_5          : 1405,
  SC_1_4_6          : 1406,
  SC_1_4_7          : 1407,
  SC_1_4_8          : 1408,
  SC_1_4_9          : 1409,
  SC_2_1_1          : 2101,
  SC_2_1_2          : 2102,
  SC_2_1_3          : 2103,
  SC_2_2_1          : 2201,
  SC_2_2_2          : 2202,
  SC_2_2_3          : 2203,
  SC_2_2_4          : 2204,
  SC_2_2_5          : 2205,
  SC_2_3_1          : 2301,
  SC_2_3_2          : 2302,
  SC_2_4_1          : 2401,
  SC_2_4_2          : 2402,
  SC_2_4_3          : 2403,
  SC_2_4_4          : 2404,
  SC_2_4_5          : 2405,
  SC_2_4_6          : 2406,
  SC_2_4_7          : 2407,
  SC_2_4_8          : 2408,
  SC_2_4_9          : 2409,
  SC_2_4_10         : 2410,
  SC_3_1_1          : 3101,
  SC_3_1_2          : 3102,
  SC_3_1_3          : 3103,
  SC_3_1_4          : 3104,
  SC_3_1_5          : 3105,
  SC_3_1_6          : 3106,
  SC_3_2_1          : 3201,
  SC_3_2_2          : 3202,
  SC_3_2_3          : 3203,
  SC_3_2_4          : 3204,
  SC_3_2_5          : 3205,
  SC_3_3_1          : 3301,
  SC_3_3_2          : 3302,
  SC_3_3_3          : 3303,
  SC_3_3_4          : 3304,
  SC_3_3_5          : 3305,
  SC_3_3_6          : 3306,
  SC_4_1_1          : 4101,
  SC_4_1_2          : 4102
};

/**
 * @constant RULE_CATEGORIES
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Numercial constant representing a rule category and is bit maskable
 *
 * @example
 * OpenAjax.a11y.RULE_CATEGORIES.UNDEFINED  
 * OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO      
 * OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES
 * OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS      
 * OpenAjax.a11y.RULE_CATEGORIES.IMAGES      
 * OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT
 * OpenAjax.a11y.RULE_CATEGORIES.LINKS      
 * OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION_NAVIGATION
 * OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER
 * OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS
 */

OpenAjax.a11y.RULE_CATEGORIES = OpenAjax.a11y.RULE_CATEGORIES || {
  UNDEFINED            : 0x0000, 
  AUDIO_VIDEO          : 0x0001,  
  DATA_TABLES          : 0x0002,
  FORM_CONTROLS        : 0x0004,
  IMAGES               : 0x0008,
  KEYBOARD_SUPPORT     : 0x0010,
  LINKS                : 0x0020,
  STRUCTURE_NAVIGATION : 0x0040,
  STYLE_READING_ORDER  : 0x0080,
  WIDGETS_SCRIPTS      : 0x0100,
  // Composite categories
  ALL                  : 0x01FF 
};

/**
 * @constant RULE_SUMMARY
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Numercial constant representing a rule summary option
 *
 * @example
 * OpenAjax.a11y.RULE_SUMMARY.UNDEFINED  
 * OpenAjax.a11y.RULE_SUMMARY.CATEGORIES  
 * OpenAjax.a11y.RULE_SUMMARY.WCAG20  
 * OpenAjax.a11y.RULE_SUMMARY.GUIDELINE  
 * OpenAjax.a11y.RULE_SUMMARY.RULESET_TYPE
 */
OpenAjax.a11y.RULE_SUMMARY = OpenAjax.a11y.RULE_SUMMARY || {
  UNDEFINED        : 0,
  CATEGORIES       : 1,
  WCAG20           : 10,
  PRINCIPLES       : 11,
  GUIDELINES       : 12,
  SUCCESS_CRITERIA : 13,
  RULESET_TYPE     : 20
};

/**
 * @constant ELEMENT_TYPE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Numercial constant representing a element type option
 *
 * @example
 * OpenAjax.a11y.ELEMENT_TYPE.UNKNOWN  
 * OpenAjax.a11y.ELEMENT_TYPE.ALL   
 * OpenAjax.a11y.ELEMENT_TYPE.AUDIO   
 * OpenAjax.a11y.ELEMENT_TYPE.AUDIO_VIDEO      
 * OpenAjax.a11y.ELEMENT_TYPE.FORM_CONTROLS      
 * OpenAjax.a11y.ELEMENT_TYPE.HEADINGS      
 * OpenAjax.a11y.ELEMENT_TYPE.HEADINGS_LANDMARKS      
 * OpenAjax.a11y.ELEMENT_TYPE.IMAGES      
 * OpenAjax.a11y.ELEMENT_TYPE.LANDMARKS      
 * OpenAjax.a11y.ELEMENT_TYPE.LANGUAGE      
 * OpenAjax.a11y.ELEMENT_TYPE.LINKS      
 * OpenAjax.a11y.ELEMENT_TYPE.LISTS      
 * OpenAjax.a11y.ELEMENT_TYPE.TABLES      
 * OpenAjax.a11y.ELEMENT_TYPE.TEXT      
 * OpenAjax.a11y.ELEMENT_TYPE.TIMING      
 * OpenAjax.a11y.ELEMENT_TYPE.VIDEO      
 * OpenAjax.a11y.ELEMENT_TYPE.WIDGETS      
 */
OpenAjax.a11y.ELEMENT_TYPE = OpenAjax.a11y.ELEMENT_TYPE || {
  UNDEFINED          : 0,
  ALL                : 1,
  ABBREVIATIONS      : 2,
  AUDIO              : 3,
  AUDIO_VIDEO        : 4,
  FORM_CONTROLS      : 5,
  HEADINGS           : 6,
  IMAGES             : 7,
  LANGUAGE           : 8,
  LANDMARKS          : 9,
  LAYOUT             : 10,
  LINKS              : 11,
  LISTS              : 12,
  TABLES             : 13,
  TEXT               : 14,
  TIMING             : 15,
  VIDEO              : 16,
  WIDGETS            : 17,
  HEADINGS_LANDMARKS : 100,
  LAYOUT_TABLES      : 101
};


/**
 * @constant CACHE_NAMES
 * @memberOf OpenAjax.a11y
 * @type Array
 * @desc Property names of specialized caches 
 */

OpenAjax.a11y.CACHE_NAMES = ['abbreviations_cache', 
                             'color_contrast_cache', 
                             'controls_cache', 
                             'headings_landmarks_cache',
                             'images_cache',
                             'keyboard_focus_cache',
                             'languages_cache',
                             'links_cache',
                             'lists_cache',
                             'media_cache',
                             'tables_cache',
                             'text_cache'];
  
/**
 * @constant PROGRESS
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Progress log constants
 *
 * @example
 * OpenAjax.a11y.PROGRESS.UNDEFINED  
 * OpenAjax.a11y.PROGRESS.START      
 * OpenAjax.a11y.PROGRESS.CACHE_START
 * OpenAjax.a11y.PROGRESS.CACHE_END  
 * OpenAjax.a11y.PROGRESS.REQUIREMENT    
 * OpenAjax.a11y.PROGRESS.RULE          
 * OpenAjax.a11y.PROGRESS.COMPLETE   
 */
OpenAjax.a11y.PROGRESS = OpenAjax.a11y.PROGRESS || {
  UNDEFINED   : 0,
  START       : 1,
  CACHE_START : 2,
  CACHE_END   : 3,
  REQUIREMENT : 4,   
  RULE        : 5,   
  COMPLETE    : 6   
};

/**
 * @constant WCAG20_LEVEL
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants related to the level of importance of a success criteria 
 * @example
 * OpenAjax.a11y.WCAG20_LEVEL.A
 * OpenAjax.a11y.WCAG20_LEVEL.AA
 * OpenAjax.a11y.WCAG20_LEVEL.AAA
 */ 
OpenAjax.a11y.WCAG20_LEVEL = OpenAjax.a11y.WCAG20_LEVEL || {
  A       : 4,
  AA      : 2,
  AAA     : 1,
  UNKNOWN : 0
};

/**
 * @constant EVALUATION_LEVELS
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants related to the level of rules that wil be evaluated  
 *       These are a bit mask for WCAG20_LEVEL constants
 * @example
 * OpenAjax.a11y.EVALUATION_LEVELS.A
 * OpenAjax.a11y.EVALUATION_LEVELS.A_AA
 * OpenAjax.a11y.EVALUATION_LEVELS.A_AA_AAA
 */ 

OpenAjax.a11y.EVALUATION_LEVELS = OpenAjax.a11y.EVALUATION_LEVELS || {
  A        : 4,
  A_AA     : 6,
  A_AA_AAA : 7
};



/**
 * @constant RULE_GROUP
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Defines a grouping of rules 
 *
 * @example
 * OpenAjax.a11y.RULE_GROUP.RULE_CATEGORIES               
 * OpenAjax.a11y.RULE_GROUP.WCAG20            
 * OpenAjax.a11y.RULE_GROUP.ALL_RULE_LIST           
 */
OpenAjax.a11y.RULE_GROUP = OpenAjax.a11y.RULE_GROUP || {
  RULE_CATEGORIES : 1,
  WCAG20          : 2,
  ALL_RULE_LIST   : 3
};


/**
 * @constant RULE_SCOPE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Defines a required or recommended rule 
 *
 * @example
 * OpenAjax.a11y.RULE_SCOPE.UNKNOWN               
 * OpenAjax.a11y.RULE_SCOPE.NODE (deprecated)               
 * OpenAjax.a11y.RULE_SCOPE.ELEMENT               
 * OpenAjax.a11y.RULE_SCOPE.PAGE               
 */
OpenAjax.a11y.RULE_SCOPE = OpenAjax.a11y.RULE_SCOPE || {
  UNKNOWN : 0,
  ELEMENT : 1,
  PAGE    : 2
};


/**
 * @constant TEST_RESULT
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Types of node results from an evaluation  
 *
 * @example
 * OpenAjax.a11y.TEST_RESULT.FAIL
 * OpenAjax.a11y.TEST_RESULT.HIDDEN
 * OpenAjax.a11y.TEST_RESULT.MANUAL_CHECK
 * OpenAjax.a11y.TEST_RESULT.NONE
 * OpenAjax.a11y.TEST_RESULT.PAGE
 * OpenAjax.a11y.TEST_RESULT.PASS
 */
OpenAjax.a11y.TEST_RESULT = OpenAjax.a11y.TEST_RESULT || {
  PASS         : 1,
  FAIL         : 2,
  MANUAL_CHECK : 3,
  HIDDEN       : 4,
  PAGE         : 5,
  NONE         : 6
};

/**
 * @constant RESULT_VALUE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Result types of a rule results 
 *
 * @example
 * OpenAjax.a11y.RESULT_VALUE.HIDDEN           
 * OpenAjax.a11y.RESULT_VALUE.MANUAL_CHECK
 * OpenAjax.a11y.RESULT_VALUE.NONE    
 * OpenAjax.a11y.RESULT_VALUE.NOT_EVALUATED    
 * OpenAjax.a11y.RESULT_VALUE.PAGE        
 * OpenAjax.a11y.RESULT_VALUE.PASS             
 * OpenAjax.a11y.RESULT_VALUE.VIOLATION        
 * OpenAjax.a11y.RESULT_VALUE.WARNING          
 */
OpenAjax.a11y.RESULT_VALUE = OpenAjax.a11y.RESULT_VALUE || {
  NONE           : 0,
  NOT_APPLICABLE : 1,
  HIDDEN         : 2,  // Content is hidden and not tested for accessibility
  PASS           : 3,
  MANUAL_CHECK   : 4,
  WARNING        : 5,
  VIOLATION      : 6,
  PAGE           : 7  // Element is part of a page level rule, but does not have rule result
};

/** 
 * @constant STATUS
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Status of rule acceptance for inclusion in the public ruleset
 *
 * @example
 * OpenAjax.a11y.STATUS.UNDEFINED
 * OpenAjax.a11y.STATUS.PROPOSED 
 * OpenAjax.a11y.STATUS.ACCEPTED
 * OpenAjax.a11y.STATUS.DEPRICATED
 */
OpenAjax.a11y.STATUS = OpenAjax.a11y.STATUS || {
  UNDEFINED  : 0,
  PROPOSED   : 1,
  ACCEPTED   : 2,
  DEPRICATED : 3
};

/**
 * @constant REFERENCES
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Types of reference for supplemential materials to help people understand an accessibility requirement and
 *       how to improve the accessibility
 * @example
 * OpenAjax.a11y.REFERENCES.UNKNOWN          
 * OpenAjax.a11y.REFERENCES.SPECIFICATION     
 * OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE        
 * OpenAjax.a11y.REFERENCES.TECHNIQUE        
 * OpenAjax.a11y.REFERENCES.EXAMPLE 
 * OpenAjax.a11y.REFERENCES.MANUAL_CHECK
 * OpenAjax.a11y.REFERENCES.AUTHORING_TOOL     
 * OpenAjax.a11y.REFERENCES.OTHER         
 */ 
 
OpenAjax.a11y.REFERENCES = OpenAjax.a11y.REFERENCES || {
  UNKNOWN         : 0,
  AUTHORING_TOOL  : 1,
  EXAMPLE         : 2,
  LIBRARY_PRODUCT : 3,
  MANUAL_CHECK    : 4,
  OTHER           : 5,
  PURPOSE         : 6,
  RULE_CATEGORY   : 7,
  REFERENCE       : 8,
  SPECIFICATION   : 9,
  TECHNIQUE       : 10,
  WCAG_TECHNIQUE  : 11
};

/**
 * @constant VISIBILITY
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Visbility of an item in graphical renderings and to asssitive technologies
 * @example
 * OpenAjax.a11y.VISIBILITY.UNKNOWN
 * OpenAjax.a11y.VISIBILITY.HIDDEN 
 * OpenAjax.a11y.VISIBILITY.VISIBLE
 */
OpenAjax.a11y.VISIBILITY = OpenAjax.a11y.VISIBILITY || {
  UNKNOWN : 1,
  HIDDEN  : 2,
  VISIBLE : 3
};

/**
 * @constant ID
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc ID of an item 
 * @example
 * OpenAjax.a11y.ID.NOT_DEFINED
 * OpenAjax.a11y.ID.UNIQUE
 * OpenAjax.a11y.ID.NOT_UNIQUE
 */
OpenAjax.a11y.ID = OpenAjax.a11y.ID || {
  NOT_DEFINED  : 1,
  UNIQUE       : 2,
  NOT_UNIQUE   : 3
};


/**
 * @constant LIST
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants related to the lists cache 
 * @example
 * OpenAjax.a11y.LIST.CONTAINER
 * OpenAjax.a11y.LIST.ITEM
 * OpenAjax.a11y.LIST.LANDMARK
 */ 
OpenAjax.a11y.LIST = OpenAjax.a11y.LIST || {
  UNDEFINED : 0,
  CONTAINER : 1,
  ITEM      : 2,
  LANDMARK  : 3
};

/**
 * @constant MEDIA
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants related to the probability of being media object with audio and video 
 * @example
 * OpenAjax.a11y.MEDIA.UNDEFINED
 * OpenAjax.a11y.MEDIA.NO
 * OpenAjax.a11y.MEDIA.MAYBE
 * OpenAjax.a11y.MEDIA.YES 
 */ 
OpenAjax.a11y.MEDIA = OpenAjax.a11y.MEDIA || {
  UNDEFINED : 0,
  NO        : 1,
  MAYBE     : 2,
  YES       : 3
};

/**
 * @constant URL_RESULT
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Staus of rule acceptance for inclusion in the public ruleset
 * @example
 * OpenAjax.a11y.URL_RESULT.INVALID
 * OpenAjax.a11y.URL_RESULT.VALID
 * OpenAjax.a11y.URL_RESULT.NOT_TESTED
 * OpenAjax.a11y.URL_RESULT.ERROR 
 */
OpenAjax.a11y.URL_RESULT = OpenAjax.a11y.URL_RESULT || {
  INVALID    :  1,
  VALID      :  2,
  NOT_TESTED :  3,
  ERROR      :  4
};

/**
 * @constant SOURCE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc What markup was used as the source for calculating the accessible name  
 * @example
 * OpenAjax.a11y.SOURCE.NONE
 * OpenAjax.a11y.SOURCE.LABEL_REFERENCE
 * OpenAjax.a11y.SOURCE.LABEL_ENCAPSULATION
 * OpenAjax.a11y.SOURCE.TITLE_ATTRIBUTE
 * OpenAjax.a11y.SOURCE.VALUE_ATTRIBUTE
 * OpenAjax.a11y.SOURCE.ALT_ATTRIBUTE
 * OpenAjax.a11y.SOURCE.BUTTON_TYPE
 * OpenAjax.a11y.SOURCE.TEXT_CONTENT
 * OpenAjax.a11y.SOURCE.ARIA_LABELLEDBY
 * OpenAjax.a11y.SOURCE.ARIA_LABEL        
 */
OpenAjax.a11y.SOURCE = OpenAjax.a11y.SOURCE || {
  NONE                 : 1,
  LABEL_REFERENCE      : 2,
  LABEL_ENCAPSULATION  : 3,
  TITLE_ATTRIBUTE      : 4,
  VALUE_ATTRIBUTE      : 5,
  ALT_ATTRIBUTE        : 6,
  BUTTON_TYPE          : 7,
  TEXT_CONTENT         : 8,
  ARIA_LABELLEDBY      : 9,
  ARIA_LABEL           : 10
};

/**
 * @constant HEADER_SOURCE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc What markup was used as the source for table cell headers
 * @example
 * OpenAjax.a11y.HEADER_SOURCE.NONE
 * OpenAjax.a11y.HEADER_SOURCE.HEADERS_ATTRIBUTE
 * OpenAjax.a11y.HEADER_SOURCE.ROW_OR_COLUMN_HEADERS
 */
OpenAjax.a11y.HEADER_SOURCE = OpenAjax.a11y.HEADER_SOURCE || {
  NONE                  : 1,
  HEADERS_ATTRIBUTE     : 2,
  ROW_OR_COLUMN_HEADERS : 3
};


/**
 * @constant CONTROL_TYPE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Indentify the cache control element type 
 *
 * @example
 * OpenAjax.a11y.CONTROL_TYPE.UNKNOWN  
 * OpenAjax.a11y.CONTROL_TYPE.BUTTON_ELEMENT   
 * OpenAjax.a11y.CONTROL_TYPE.BUTTON_INPUT   
 * OpenAjax.a11y.CONTROL_TYPE.CHECKBOX   
 * OpenAjax.a11y.CONTROL_TYPE.FIELDSET 
 * OpenAjax.a11y.CONTROL_TYPE.FORM     
 * OpenAjax.a11y.CONTROL_TYPE.HIDDEN   
 * OpenAjax.a11y.CONTROL_TYPE.IMAGE    
 * OpenAjax.a11y.CONTROL_TYPE.LABEL    
 * OpenAjax.a11y.CONTROL_TYPE.OPTION   
 * OpenAjax.a11y.CONTROL_TYPE.OPTGROUP   
 * OpenAjax.a11y.CONTROL_TYPE.PASSWORD 
 * OpenAjax.a11y.CONTROL_TYPE.RADIO    
 * OpenAjax.a11y.CONTROL_TYPE.RESET    
 * OpenAjax.a11y.CONTROL_TYPE.SELECT   
 * OpenAjax.a11y.CONTROL_TYPE.SUBMIT   
 * OpenAjax.a11y.CONTROL_TYPE.TEXT     
 * OpenAjax.a11y.CONTROL_TYPE.TEXTAREA 
 * OpenAjax.a11y.CONTROL_TYPE.WIDGET 
 */
OpenAjax.a11y.CONTROL_TYPE = OpenAjax.a11y.CONTROL_TYPE || {
  UNKNOWN        : 1,
  BUTTON_ELEMENT : 2,
  BUTTON_INPUT   : 3,
  CHECKBOX       : 4,
  FIELDSET       : 5,
  FILE           : 6,
  FORM           : 7,
  HIDDEN         : 8,
  IMAGE          : 9,
  LABEL          : 10,
  OPTION         : 11,
  OPTGROUP       : 12,
  PASSWORD       : 13,
  RADIO          : 14,
  RESET          : 15,
  SELECT         : 16,
  SUBMIT         : 17,
  TEXT           : 18,
  TEXTAREA       : 19,
  WIDGET         : 20
};

/**
 * @constant MAIN
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants for MAIN cache elements  
 * @example
 * OpenAjax.a11y.MAIN.ROLE_MAIN
 * OpenAjax.a11y.MAIN.H1_ELEMENT
 * OpenAjax.a11y.MAIN.TITLE_ELEMENT   
 */
OpenAjax.a11y.MAIN = OpenAjax.a11y.MAIN || {
  ROLE_MAIN      :  1,
  H1_ELEMENT     :  2,
  TITLE_ELEMENT  :  3
};

/**
 * @constant TABLE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants for TABLE cache elements
 * @example
 * OpenAjax.a11y.TABLE.TABLE_ELEMENT   
 * OpenAjax.a11y.TABLE.CAPTION_ELEMENT 
 * OpenAjax.a11y.TABLE.THEAD_ELEMENT   
 * OpenAjax.a11y.TABLE.TBODY_ELEMENT   
 * OpenAjax.a11y.TABLE.TR_ELEMENT      
 * OpenAjax.a11y.TABLE.TH_ELEMENT      
 * OpenAjax.a11y.TABLE.TD_ELEMENT      
 */
OpenAjax.a11y.TABLE = OpenAjax.a11y.TABLE || {
  TABLE_ELEMENT   :  1,
  CAPTION_ELEMENT :  2,
  THEAD_ELEMENT   :  3,
  TBODY_ELEMENT   :  4,
  TR_ELEMENT      :  5,
  TH_ELEMENT      :  6,
  TD_ELEMENT      :  7
};

/**
 * @constant LINK_TYPE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Constants for LINK cache elements
 * @example
 * OpenAjax.a11y.LINK_TYPE.EMPTY
 * OpenAjax.a11y.LINK_TYPE.OTHER
 * OpenAjax.a11y.LINK_TYPE.INTERNAL
 * OpenAjax.a11y.LINK_TYPE.HTTP
 * OpenAjax.a11y.LINK_TYPE.HTTPS
 * OpenAjax.a11y.LINK_TYPE.FTP
 * OpenAjax.a11y.LINK_TYPE.FTS
 * OpenAjax.a11y.LINK_TYPE.FILE
 * OpenAjax.a11y.LINK_TYPE.JAVASCRIPT
 * OpenAjax.a11y.LINK_TYPE.MAILTO
 * OpenAjax.a11y.LINK_TYPE.TARGET
 */
OpenAjax.a11y.LINK_TYPE = OpenAjax.a11y.LINK_TYPE || {
  EMPTY      : 0,
  OTHER      : 1,
  INTERNAL   : 2,
  HTTP       : 3,
  HTTPS      : 4,
  FTP        : 5,
  FTPS       : 6,
  FILE       : 7,
  JAVASCRIPT : 8,
  MAILTO     : 9,
  TARGET     : 10
};

/**
 * @constant FILTERED_RULE_RESULT_RETURN_VALUE
 * @memberOf OpenAjax.a11y
 * @type Number
 * @desc Used for rule aggregation
 * @example
 * OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE.NO_MATCH
 * OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE.ADDED
 * OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE.NOT_ADDED
 */
OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE = OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE || {
  NO_MATCH  : 0,
  ADDED     : 1,
  NOT_ADDED : 2
};/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*              ARIA Defintions and Validation Methods              */
/* ---------------------------------------------------------------- */


if (typeof OpenAjax.a11y.aria == "undefined") {
	OpenAjax.a11y.aria = {
			
		/*
		 * array of WAI-ARIA global states and properties
		 * @see http://www.w3.org/TR/wai-aria/#global_states
		 */
		globalProperties : [
            "aria-atomic", 
            "aria-busy", 
            "aria-controls", 
            "aria-describedby",
            "aria-disabled", 
            "aria-dropeffect", 
            "aria-flowto", 
            "aria-grabbed",
            "aria-haspopup", 
            "aria-hidden", 
            "aria-invalid", 
            "aria-label",
            "aria-labelledby", 
            "aria-live", 
            "aria-owns", 
            "aria-relevant"
        ],
	
        /*
         * XSD data types for all WAI-ARIA properties
         * along with valid values when the data type is NMTOKEN
         */
        propertyDataTypes : {
         	"aria-activedescendant" : {
         		type : "idref"
         	},
         	"aria-atomic" : {
         		type : "boolean"
         	},
         	"aria-autocomplete" : {
         		type : "nmtoken",
         		values : ["inline", "list", "both", "none"]
         	},
         	"aria-busy" : {
         		type : "boolean"
         	},
         	"aria-checked" : {
         		type : "nmtoken",
         		values : ["true", "false", "mixed", "undefined"]
         	},
         	"aria-controls" : {
         		type : "idrefs"
         	},
         	"aria-describedby" : {
         		type : "idrefs"
         	},
         	"aria-disabled" : {
         		type : "boolean"
         	},
         	"aria-dropeffect" : {
         		type : "nmtokens",
         		values : ["copy", "move", "link", "execute", "popup", "none"]
         	},
         	"aria-expanded" : {
         		type : "nmtoken",
         		values : ["true", "false", "undefined"]
         	},
         	"aria-flowto" : {
         		type : "idrefs"
         	},
         	"aria-grabbed" : {
         		type : "nmtoken",
         		values : ["true", "false", "undefined"]
         	},
         	"aria-haspopup" : {
         		type : "boolean"
         	},
         	"aria-hidden" : {
         		type : "boolean"
         	},
         	"aria-invalid" : {
         		type : "nmtoken",
         		values : ["true", "false", "spelling", "grammar"]
         	},
         	"aria-label" : {
         		type : "string"
         	},
         	"aria-labelledby" : {
         		type : "idrefs"
         	},
         	"aria-level" : {
         		type : "integer"
         	},
         	"aria-live" : {
         		type : "nmtoken",
         		values : ["off", "polite", "assertive"]
         	},
         	"aria-multiline" : {
         		type : "boolean"
         	},
         	"aria-multiselectable" : {
         		type : "boolean"
         	},
         	"aria-orientation" : {
         		type : "nmtoken",
         		values : ["vertical", "horizontal"]
         	},
         	"aria-owns" : {
         		type : "idrefs"
         	},
         	"aria-posinset" : {
         		type : "integer"
         	},
         	"aria-pressed" : {
         		type : "nmtoken",
         		values : ["true", "false", "mixed", "undefined"]
         	},
         	"aria-readonly" : {
         		type : "boolean"
         	},
         	"aria-relevant" : {
         		type : "nmtokens",
         		values : ["additions", "removals", "text", "all"]
         	},
         	"aria-required" : {
         		type : "boolean"
         	},
         	"aria-selected" : {
         		type : "nmtoken",
         		values : ["true", "false", "undefined"]
         	},
         	"aria-setsize" : {
         		type : "integer"
         	},
         	"aria-sort" : {
         		type : "nmtoken",
         		values : ["ascending", "descending", "other", "none"]
         	},
         	"aria-valuemax" : {
         		type : "decimal"
         	},
         	"aria-valuemin" : {
         		type : "decimal"
         	},
         	"aria-valuenow" : {
         		type : "decimal"
         	},
         	"aria-valuetext" : {
         		type : "string"
         	}
        },
        
        /*
         * list of abstract roles - used to support the WAI-ARIA role taxonomy and 
         * not to be used by content authors
         * @see http://www.w3.org/TR/wai-aria/roles#isAbstract
         */
        abstractRoles : [
            "command", 
            "composite", 
            "input", 
            "landmark", 
            "range",
            "roletype", 
            "section", 
            "sectionhead", 
            "select",
            "structure", 
            "widget", 
            "window"
        ],
                         
          /*
           * design patterns for concrete WAI-ARIA roles
           * legitimate keys for each role include:
           * 
           * - container: appropriate container(s) for that role
           * - props: states and properties that may be associated with this role (in addition to the global states and properties listed above)
           * - reqProps: required states or properties for this role
           * - reqChildren: required children for this role
           * - htmlEquiv: HTML equivalent for this role
           * - roleType: one of widget, landmark, or null 
           */
        designPatterns : {
         		
         	"alert" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "live"     		
         	},
         	                
         	"alertdialog" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"application" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"article" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
          		nameFromContent: false,
         		roleType : "section"
        	},
         	
         	"banner" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"button" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded", "aria-pressed"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "input[@type='button']",
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"checkbox" : {
         		reqName : true,
         		container : null,
         		props : null,
         		reqProps : ["aria-checked"],
         		reqChildren : null,
         		htmlEquiv : "input[@type='checkbox']",
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
             "columnheader" : {
         		reqName : true,
         		container : ["row"],
         		props : ["aria-expanded", "aria-sort", "aria-readonly", "aria-selected", "aria-required"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "th",
         		nameFromContent: true,
         		roleType : "section"
         	},
         	
         	"combobox" : {
         		reqName : true,
         		container : null,
         		props : ["aria-autocomplete", "aria-required", "aria-activedescendant"],
         		reqProps : ["aria-expanded"],
         		reqChildren : ["listbox", "textbox"],
         		htmlEquiv : "select",
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"complementary" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : ["aria-labelledby"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"contentinfo" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : ["aria-labelledby"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"definition" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"dialog" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"directory" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		roleType : "section"
         	},
         	
         	"document" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
          		nameFromContent: false,
         		roleType : "section"
        	},
         	
         	"form" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "form",
         		nameFromContent: false,
         		roleType : "landmark"
         	},	
         	
         	"grid" : {
         		reqName : true,
         		container : null,
         		props : ["aria-level", "aria-multiselectable", "aria-readonly", "aria-activedescendant", "aria-expanded"],
         		reqProps : null,
         		reqChildren : ["row", "rowgroup"],
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"gridcell" : {
         		reqName : true,
         		container : ["row"],
         		props : ["aria-readonly", "aria-selected", "aria-expanded", "aria-required"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		roleType : "widget"         		
         	},
         	
         	"group" : {
         		reqName : false,
         		container : null,
         		props : ["aria-activedescendant", "aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "fieldset",
         		nameFromContent: false,
         		roleType : "section"         		
         	},
         	
         	"heading" : {
         		reqName : true,
         		container : null,
         		props : ["aria-level", "aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "h1 | h2 | h3 | h4 | h5 |h6",
         		nameFromContent: true,
         		roleType : "section"         		
         	},
         	
         	"img" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "img",
         		nameFromContent: false,
         		roleType : "section"         		
         	},
         	
         	"link" : {
         		reqName : true,
         		container : null,
         		props : null,
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "a",
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"list" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : ["group", "listitem"],
         		htmlEquiv : "ul | ol",
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"listbox" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded", "aria-activedescendant", "aria-multiselectable", "aria-required"],
         		reqProps : null,
         		reqChildren : ["option"],
         		htmlEquiv : "select",
         		nameFromContent: false,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"listitem" : {
         		reqName : true,
         		container : ["list"],
         		props : ["aria-expanded", "aria-level", "aria-posinset", "aria-setsize"],
         		reqProps : null,
         		reqChildren : null,
         		nameFromContent: true,
         		htmlEquiv : "section",
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"log" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "live"
         	},
         	
         	"main" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"marquee" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"math" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"menu" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded", "aria-activedescendant"],
         		reqProps : null,
         		reqChildren : ["menuitem", "menuitemcheckbox", "menuitemradio"],
         		htmlEquiv : null,
         		nameFromContent: false,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"menubar" : {
         		reqName : false,
         		container : null,
         		props : ["aria-activedescendant", "aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"menuitem" : {
         		reqName : true,
         		container : ["menu", "menubar"],
         		props : null,
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"menuitemcheckbox" : {
         		reqName : true,
         		container : ["menu", "menubar"],
         		props : null,
         		reqProps : ["aria-checked"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"menuitemradio" : {
         		reqName : true,
         		container : ["menu", "menubar"],
         		props : ["aria-selected", "aria-posinset", "aria-setsize"],
         		reqProps : ["aria-checked"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"navigation" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : ["aria-labelledby"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"note" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"option" : {
         		reqName : true,
         		container : ["listbox"],
         		props : ["aria-expanded", "aria-checked", "aria-selected", "aria-posinset", "aria-setsize"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"presentation" : {
         		reqName : false,
         		container : null,
         		props : null,
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"progressbar" : {
         		reqName : true,
         		container : null,
         		props : ["aria-valuetext", "aria-valuenow", "aria-valuemax", "aria-valuemin"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget",
         		hasRange: true
         	},
         	
         	"radio" : {
         		reqName : true,
         		container : null,
         		props : ["aria-selected", "aria-posinset", "aria-setsize"],
         		reqProps : ["aria-checked"],
         		reqChildren : null,
         		htmlEquiv : "input[@type='radio']",
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"radiogroup" : {
         		reqName : true,
         		container : null,
         		props : ["aria-activedescendant", "aria-expanded", "aria-required"],
         		reqProps : ["aria-labelledby"],
         		reqChildren : ["radio"],
         		htmlEquiv : null,
         		nameFromContent: false,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"region" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "frame",
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"row" : {
         		reqName : false,
         		container : ["grid", "treegrid", "rowgroup"],
         		props : ["aria-level", "aria-selected", "aria-activedescendant", "aria-expanded"],
         		reqProps : null,
         		reqChildren : ["gridcell", "rowheader", "columnheader"],
         		htmlEquiv : null,
         		nameFromContent: true,
         		roleType : "section"
         	},
         	
         	"rowgroup" : {
         		reqName : false,
         		container : ["grid"],
         		props : ["aria-expanded", "aria-activedescendant"],
         		reqProps : null,
         		reqChildren : ["row"],
         		htmlEquiv : null,
         		nameFromContent: true,
         		roleType : "section"
         	},
         	
         	"rowheader" : {
         		reqName : true,
         		container : ["row"],
         		props : ["aria-expanded", "aria-sort", "aria-required", "aria-readonly", "aria-selected"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "th",
         		nameFromContent: true,
         		roleType : "section"
         	},
         	
         	"scrollbar" : {
         		reqName : true,
         		container : null,
         		props : ["aria-valuetext"],
         		reqProps : ["aria-controls", "aria-orientation", "aria-valuenow", "aria-valuemax", "aria-valuemin"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget",
         		hasRange: true
         	},
         	
         	"search" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : ["aria-labelledby"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "landmark"
         	},
         	
         	"separator" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded", "aria-orientation"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"slider" : {
         		reqName : true,
         		container : null,
         		props : ["aria-orientation", "aria-valuetext"],
         		reqProps : ["aria-valuemax", "aria-valuenow", "aria-valuemin"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget",
         		hasRange: true
         	},
         	
         	"spinbutton" : {
         		reqName : true,
         		container : null,
         		props : ["aria-required", "aria-valuetext"],
         		reqProps : ["aria-valuemax", "aria-valuenow", "aria-valuemin"],
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget",
         		hasRange: true
         	},
         	
         	"status" : {
         		reqName : false,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "live"
         	},
         	
         	"tab" : {
         		reqName : true, // bug in authoring spec??
         		container : ["tablist"],
         		props : ["aria-selected", "aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		roleType : "widget"
         	},
         	
         	"tablist" : {
         		reqName : false,
         		container : null,
         		props : ["aria-activedescendant", "aria-expanded", "aria-level"],
         		reqProps : null,
         		reqChildren : ["tab"],
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"tabpanel" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"textbox" : {
         		reqName : true,
         		container : null,
         		props : ["aria-activedescendant", "aria-autocomplete", "aria-multiline", "aria-readonly", "aria-required"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : "input[@type='text'] | textarea",
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"timer" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "live"
         	},
         	
         	"toolbar" : {
         		reqName : false,
         		container : null,
         		props : ["aria-activedescendant", "aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		supportOnClick: true,
         		roleType : "widget"
         	},
         	
         	"tooltip" : {
         		reqName : true,
         		container : null,
         		props : ["aria-expanded"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"tree" : {
         		reqName : true,
         		container : null,
         		props : ["aria-multiselectable", "aria-activedescendant", "aria-expanded", "aria-required"],
         		reqProps : null,
         		reqChildren : ["group", "treeitem"],
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "widget"
         	},
         	
         	"treegrid" : {
         		reqName : true,
         		container : null,
         		props : ["aria-activedescendant", "aria-expanded", "aria-level", "aria-multiselectable", "aria-readonly", "aria-required"],
         		reqProps : null,
         		reqChildren : ["row"],
         		htmlEquiv : null,
         		nameFromContent: false,
         		roleType : "section"
         	},
         	
         	"treeitem" : {
         		reqName : true,
         		container : ["group", "tree"],
         		props : ["aria-checked", "aria-selected", "aria-expanded", "aria-level", "aria-posinset", "aria-setsize"],
         		reqProps : null,
         		reqChildren : null,
         		htmlEquiv : null,
         		nameFromContent: true,
         		supportOnClick: true,
         		roleType : "widget"
            }
         	
        }, // end designPatterns
        
        getRoleObject : function(role) {

          var dp = this.designPatterns;

          for (var r in dp) {
          
            if (role == r)  return dp[r];
          
          }

          var ar = this.abstractRoles;
          var ar_len = ar.length;
          
          for (var i = 0; i < ar_len; i++) {
          
            if (role == r[i])  return 'abstract';
          
          }

          return null;
        }
        
    };	    
    
}
/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*        Utilities and String Extensions                           */
/* ---------------------------------------------------------------- */

/** 
 * @namespace OpenAjax.a11y.util
 */

OpenAjax.a11y.util = OpenAjax.a11y.util || {};

/**
 * @function escapeForJSON
 *
 * @memberOf OpenAjax.a11y.util
 *
 * @desc Returns an safe string for JSON output that has single quotes and new line characters
 *
 * @param  {String}  str - string to escape 
 *
 * @return {String}  Escaped string
 */
 
OpenAjax.a11y.util.escapeForJSON = function(str) {

  if (typeof str === 'string' && str.length > 0) {
    var return_str = str.replace("'", "\\'", "gi");
    return_str = str.replace('"', "'", "gi");
    return_str = return_str.replace("\n", "\\n", "gi");
    return return_str;
  }
  return str;  
};

/**
 * @function removeEscapesFromJSON
 *
 * @memberOf OpenAjax.a11y.util
 *
 * @desc Returns an unescaped string from a JSON string that has been escaped for single quotes and new line characters
 *
 * @param  {String}  str - string to un escape 
 *
 * @return {String}  String with escape characters removed
 */
 
OpenAjax.a11y.util.removeEscapesFromJSON = function(str) {

  if (typeof str === 'string' && str.length > 0) {
    var return_str = str.replace("\\'", "'", "gi");
    return_str = return_str.replace("\\n", "\n", "gi");
    return return_str;
  }
  return str;  
};

/**
 * @function getFormattedDate
 *
 * @memberOf OpenAjax.a11y.util
 *
 * @desc Returns a fomratted string (YYYY-MM-DD) represeting the current date
 *       with leading zeros
 *
 * @return {String}  Formatted date string
 */
 
OpenAjax.a11y.util.getFormattedDate = function(str) {

  function leadingZero(n) {
    var n1 = n.toString();
    if (n < 10) n1 = "0" + n;
    return n1;
  }

  var date = new Date();
  
  var y = date.getFullYear();
  var m = date.getMonth() + 1;
  var d = date.getDate();
  var hours = date.getHours() + 1;
  var minutes = date.getMinutes() + 1;

  return y + "-" + leadingZero(m) + "-" + leadingZero(d) + ":" + leadingZero(hours)+ ":" + leadingZero(minutes);

};


/**
 * @function getStringUsingURL
 *
 * @memberOf OpenAjax.a11y.util
 *
 * @desc Reads a URL into a string
 *       Used with creating HTML reports
 *
 * @param  {String}  url     - url to file 
 */
 
OpenAjax.a11y.util.initStringUsingURL = function(url) {

  var xmlhttp = new XMLHttpRequest();

//  OpenAjax.a11y.logger.debug( "REQUESTING URL: " + url);

  xmlhttp.open("GET", url, false);
  xmlhttp.send(null); 
  
  var str = xmlhttp.responseText;
  
//  OpenAjax.a11y.logger.debug( "TEXT: " + str);
  
  return str;
  
};
 


/**
 * @function transformElementMarkup
 *
 * @memberOf OpenAjax.a11y.util
 * 
 * @desc Converts element markup in strings to capitalized text (default) or adds <code> element
 *
 * @param {String}  str  - String to convert element text
 * 
 * @return  String 
 */
 
OpenAjax.a11y.util.transformElementMarkup = function(str) {
 
  var new_str = "";
  
  var transform_option = 1; // default is capitalize
  
  if (OpenAjax.a11y.ELEMENT_FORMATING == "HTML") transform_option = 2; // transform to html
  if (OpenAjax.a11y.ELEMENT_FORMATING == "NONE") transform_option = 3; // just removes @ sign from string
  
  if (str && str.length) {
    var max = str.length; 
    var transform_flag = false;
    
    for (var i = 0; i < max; i++) {
    
      var c = str[i];
    
      if (c == '@') { 
      
        if (transform_option == 2) {
          if (transform_flag) 
            new_str += '</code>';
          else             
            new_str += '<code>';
        }    
      
        transform_flag = !transform_flag;
        continue;
      }  
      
      if (transform_flag && transform_option == 1) 
        new_str += c.toUpperCase();
      else
        new_str += c;
    }
  }
  return new_str;
};







/**
 * @function urlExists
 *
 * @memberOf OpenAjax.a11y.util
 *
 * @desc Determines if a URL exits
 *
 * @param {String} url      - url to test if it exists
 *
 * @return  Number  
 */
 
OpenAjax.a11y.util.urlExists = function (url) {

 if (OpenAjax.a11y.SUPPORTS_URL_TESTING && OpenAjax.a11y.URL_TESTING_ENABLED) {
  try {
  
   var http = new XMLHttpRequest();
   http.open('HEAD', url, false);
   http.send(null);
   if (http.status!==404) {
//     OpenAjax.a11y.logger.debug( "URL: " + url + " (valid)");
     return OpenAjax.a11y.URL_RESULT.VALID;
   }
   else {
//     OpenAjax.a11y.logger.debug( "URL: " + url + " (INVALID)");
     return OpenAjax.a11y.URL_RESULT.INVALID;
   }
  }
  catch (e) {
   return OpenAjax.a11y.URL_RESULT.ERROR;
  }
 }
 else {
  return OpenAjax.a11y.URL_RESULT.NOT_TESTED;
 }
 
}; 

/**
 * @function RGBToHex
 * @memberOf OpenAjax.a11y.util
 * 
 * @desc Converts an RGB color to Hex values
 *
 * @param {String} rgb_color - RGB Color
 * 
 * @return  String 
 */
 
OpenAjax.a11y.util.RGBToHEX = function( rgb_color ) {

 function stringToHex(d) {
  var hex = Number(d).toString(16);
  if (hex.length == 1) {
   hex = "0" + hex;
  }
  return hex;
 }

 var i;
 var length; 

 if (!rgb_color) return "000000";

 var hex = [];
 var color_hex = "000000";
 var components = rgb_color.match(/[\d\.]+/g);
  
 if (components && components.length) {
  length = components.length;
  
  if (length == 3) {
   // RGB value
   for (i=0; i<3; i++) {
    hex.push(stringToHex(components[i]));
   } // end loop
 
   color_hex = hex[0] + hex[1] + hex[2]; 
   // OpenAjax.a11y.logger.debug( rgb_color + " " + color_hex );
   
  }
  else {  
   
   if (length == 4) {
    // RGBA value
    for (i=0; i<3; i++) {
     hex[i] = stringToHex(Math.round(parseFloat(components[i])*parseFloat(components[3])));
    } // end loop  
    color_hex = hex[0] + hex[1] + hex[2]; 
   }
  }
 } 
 
 return color_hex;
};


/** 
 * @namespace String
 */

 /**
 * @function isInteger
 * @memberOf String
 */

// string utilities
	
/**
 * @function normalizeSpace
 * @memberOf String
 */

if (typeof String.normalizeSpace == "undefined") {
 String.prototype.normalizeSpace = function () {
  // Replace repeated spaces, newlines and tabs with a single space
  return this.replace(/^\s*|\s(?=\s)|\s*$/g, "");
 }; // end function normalizeSpace
}


/**
 * @function replaceAll
 * @memberOf String
 */

if (typeof String.replaceAll == "undefined") {
  String.prototype.replaceAll = function(str1, str2, ignore) {
    return this.replace(new RegExp(str1.replace(/([\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, function(c){return "\\" + c;}), "g"+(ignore?"i":"")), str2);
  }; 
}/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                      AbbreviationsCache                          */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor AbbreviationsCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructor for abbreviations cache object which contains a list of 
 *    abbreviation items representing the abbreviations defined  
 *    in a document. The item also contains a list of all the 
 *    dom element objects that share the same abbreviation 
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {String}   sort_property   - Property of abbreviation item that the list is sorted on  
 * @property {Boolean}  sort_ascending  - true if list is sorted by ascending values, otherwsie false 
 *
 * @property {Array}   abbreviation_items  - List of abbreviation items 
 * @property {Number}  length              - Number of abbreviation items in list 
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */

OpenAjax.a11y.cache.AbbreviationsCache = function (dom_cache) {

  this.dom_cache  = dom_cache;
  this.up_to_date = false;
  
  this.abbreviation_items = [];
  this.length = 0;
 
  this.sort_property  = 'abbreviation_text';
  this.sort_ascending = true;
  
};

/**
 * @method addAbbreviationItem
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Adds a DOM Element object with an abbreviation to the abbreviation item list.
 *       If the abreviation item does not exist the function will create one
 *
 * @param {DOMElement}  dom_element  - dom element to add to a abbreviation list
 */

OpenAjax.a11y.cache.AbbreviationsCache.prototype.addAbbreviationItem = function (dom_element) {

 var abbreviation_item = null;
 var abbreviation_items_len = this.abbreviation_items.length;
 var found = false;
 var node_text = dom_element.getText();

 for (var i=0; i < abbreviation_items_len; i++ ) {
  abbreviation_item = this.abbreviation_items[i];
  
  if (node_text == abbreviation_item.abbreviation_text) {
    
    abbreviation_item.dom_elements.push(dom_element);
    abbreviation_item.count = abbreviation_item.dom_elements.length;
    
    found = true; 
    break;
  }
 } // end loop

 if (!found) {
  abbreviation_item = new OpenAjax.a11y.cache.AbbreviationItem(node_text);
  
  abbreviation_item.dom_elements.push(dom_element);
  abbreviation_item.count = abbreviation_item.dom_elements.length;
  abbreviation_item.cache_id = "abbrev_" + this.length;  
  
  this.abbreviation_items.push(abbreviation_item);
  this.length = this.length + 1;
 }

};

/**
 * @deprecated getAbbreviationItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Returns the abbreviation item with the cache id
 *
 * @param {String}  cache_id  - cache id of the abbreviation item object
 *
 * @return {AbbreviationItem} Returns abbreviation item object if cache id found, otherwise null  
 */

OpenAjax.a11y.cache.AbbreviationsCache.prototype.getAbbreviationItemByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Returns the abbreviation item with the cache id
 *
 * @param {String}  cache_id  - cache id of the abbreviation item object
 *
 * @return {AbbreviationItem} Returns abbreviation item object if cache id found, otherwise null  
 */

OpenAjax.a11y.cache.AbbreviationsCache.prototype.getItemByCacheId = function (cache_id) {

  var i, j;
  var ai, de;
  var dom_elements, dom_elements_len;
  
  var abbreviation_items     = this.abbreviation_items;
  var abbreviation_items_len = abbreviation_items.length;

  if (cache_id && cache_id.length) {  
  
    for (i = 0; i < abbreviation_items_len; i++) {
      ai = abbreviation_items[i];
    
      if (ai.cache_id == cache_id) return ai;
      
      dom_elements     = ai.dom_elements;
      dom_elements_len = dom_elements.length;
      
      for (j = 0; j < dom_elements_len; j++ ) {
        de = dom_elements[j];
        if (de.cache_id == cache_id) return de;
      } // end loop
    } // end loop
  } 

 return null;
};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Empties all the abbreviation items from the cache 
 */

OpenAjax.a11y.cache.AbbreviationsCache.prototype.emptyCache = function () {

 this.abbreviation_items.length = 0;
 this.sort_property = 'abbreviation_text';
 this.sort_ascending = true;
 this.up_to_date = false;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Updates the AbbreviationsCache object with information from a DOMElement object
 *    This is used during the creation of the cache and is used by the functions for
 *    either creating the cache all at one time or selectively
 *
 * @param {DOMElement}  dom_element  - DOM Element object to add to the abbreviations cache
 */
 
OpenAjax.a11y.cache.AbbreviationsCache.prototype.updateCacheItems = function (dom_element) {

 if ((dom_element.tag_name == 'abbr') ||
   (dom_element.tag_name == 'acronym')) {

   this.addAbbreviationItem(dom_element);
   
 }
 
};

/**
 * @method traverseDOMElementsForAbbreviations
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Traverses the DOMElements to update the abbreviation cache
 */
 
OpenAjax.a11y.cache.AbbreviationsCache.prototype.traverseDOMElementsForAbbreviations = function (dom_element) {

 if (!dom_element) return;

 if (dom_element.type == Node.ELEMENT_NODE) {

  this.updateCacheItems(dom_element);
  
  for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
   this.traverseDOMElementsForAbbreviations(dom_element.child_dom_elements[i]);
  } // end loop
  
 }  
  
};

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Traverses the DOMElements to update the abbreviation cache
 *    This function is used to update the abbreviation cache 
 *    when needed by a rule, it sets the up to date flag when done
 */
 
OpenAjax.a11y.cache.AbbreviationsCache.prototype.updateCache = function () {
 var i;
 var children = this.dom_cache.element_cache.child_dom_elements;
 var children_len = children.length;
 
 for (i=0; i < children_len; i++) {
  this.traverseDOMElementsForAbbreviations(children[i]);
 }  

 this.up_to_date = true;
 
};

/**
 * @method sortAbbreviationItems
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Sorts abbreviations by abbreviation_text property
 *
 * @param {Boolean}  ascending  - true if sort in ascending order; false in descending order
 *
 * @return {Boolean}  Returns true if list was sorted, false if not
 */

OpenAjax.a11y.cache.AbbreviationsCache.prototype.sortAbbreviationItems = function(ascending) {

  var swapped = false;
  var temp = null;
  var i;

  if( !this.abbreviation_items || (this.abbreviation_items.length === 0)) {
    return false;
  } // endif

  this.sort_ascending = ascending;
 
  var abbreviation_items_len = this.abbreviation_items.length;

  if( ascending ) {
    do{
      swapped = false;
      for (i = 1; i < abbreviation_items_len; i++ ) {
        if (this.abbreviation_items[i-1].abbreviation_text > this.abbreviation_items[i].abbreviation_text) {
          // swap the values
          temp = this.abbreviation_items[i-1];
          this.abbreviation_items[i-1] = this.abbreviation_items[i];
          this.abbreviation_items[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < abbreviation_items_len; i++ ) {
        if (this.abbreviation_items[i-1].abbreviation_text < this.abbreviation_items[i].abbreviation_text) {
          // swap the values
          temp = this.abbreviation_items[i-1];
          this.abbreviation_items[i-1] = this.abbreviation_items[i];
          this.abbreviation_items[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  } 

  this.sort_property = 'abbreviation_text';
 
  return true;

}; 

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationsCache
 *
 * @desc Returns a text string representation of the abbreviation cache object 
 *
 * @return {String} Returns string represention the abbreviations cache object
 */

OpenAjax.a11y.cache.AbbreviationsCache.prototype.toString = function () {

 var str ="\n\nAbbreviation Information\n";

 var list_length = this.abbreviation_items.length;
 
 for (var i=0; i < list_length; i++ ) {
  str += this.abbreviation_items[i].toString();  
 } // end loop

 return str;
};

/* ---------------------------------------------------------------- */
/*                      AbbreviationItem                            */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor AbbreviationItem
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructor for abbreviation item object which contains information
 *       about dom elements that share the same abbreviation 
 * 
 * @param  {String}  abbreviation  - text of abbreviation
 *         
 * @property  {String}  cache_id            - String that uniquely identifies the cache element in the DOMCache
 * @property  {String}  abbreviation_text   - text of abbreviation
 *
 * @property  {Array}   dom_elements  - List of dom elements associated with the abbreviation 
 * @property  {Number}  count         - Number of dom elements that share this abbreviation
 */
 
OpenAjax.a11y.cache.AbbreviationItem = function (abbreviation) {

  this.cach_id = "";
  
  this.abbreviation_text = abbreviation;
  
  this.dom_elements = [];
  this.count = 0;
   
}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.getNodeResults = function () {
  return this.dom_elements[0].getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.getStyle = function () {

  return this.dom_elements[0].getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.getAttributes = function (unsorted) {

  return [];
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.getCacheProperties = function (unsorted) {

  return [];
  
};


/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.getEvents = function () {
   
  return [];
  
};



/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.AbbreviationItem
 *
 * @desc Returns a text string representation of the abbreviation item object 
 *
 * @return {String} Returns string represention the abbreviation item object
 */

OpenAjax.a11y.cache.AbbreviationItem.prototype.toString = function () {

 return "Abbreviation: " + abbreviation_text; 
};



/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                       ColorContrstCache                          */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor ColorContrastCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
* @desc Constructor for ColorContrastCache object which contains a list of
*    color contrast items representing the color contrast combinations
*    used in a document. The item also contains a list of all the
*    DOM Element nodes that contain that color contrast combination
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {String}    sort_property   - Property of ColorContrastItem that the list is sorted on  
 * @property {Boolean}   sort_ascending  - true if list is sorted by ascending values, otherwsie false 
 *
 * @property {Array}    color_contrst_items  - List of color contrast items 
 * @property {Number}   length               - Number of color contrast items in list 
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */

OpenAjax.a11y.cache.ColorContrastCache = function (dom_cache) {
  
  this.dom_cache = dom_cache;
  this.color_contrast_items =[];
  
  this.sort_property = 'color_contrast_ratio';
  this.sort_ascending = false;
  
  this.up_to_date = false;
  this.length = 0;
  
  
};

/**
 * @method addColorContrastItem
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Adds a DOM text object information to a color contrast item in the color contrast
 *       cache, if it does not match any of the current color contrast items it will create a
 *       new color contrast item.
 *
 * @param {DOMText}  dom_text_node  - dom text_node to add to color contrast list
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.addColorContrastItem = function (dom_text_node) {
  
  var i;
  
  var cci;
  var cs;
  var color_contrast_items_len = this.color_contrast_items.length;
  var found = false;
  
  for (i = 0; i < color_contrast_items_len; i++) {
    cci = this.color_contrast_items[i];
    cs = dom_text_node.computed_style;
    
    // OpenAjax.a11y.logger.debug("color compare " + dom_text_node.computed_style.color + " with " + item.color );
    
    if ( cci && 
         cci.color &&
         (cs.color_hex     == cci.color)         &&
         (cs.is_large_font == cci.is_large_font) &&
         (cs.background_color_hex == cci.background_color) &&
         ((cs.background_image == 'none' && cci.background_image == 'none') ||
          ((cs.background_image    == cci.background_image) &&
           (cs.background_repeat   == cci.background_repeat) &&
           (cs.background_position == cci.background_position)))) {
      
      cci.dom_text_nodes.push(dom_text_node);
      cci.node_count = cci.dom_text_nodes.length;
      
      cci.addToCharacterCount(dom_text_node.text_length);
      
      found = true;
      break;
    }
  }
  // end loop
  
  if (!found) {
    cs = dom_text_node.computed_style;
    
    cci = new OpenAjax.a11y.cache.ColorContrastItem(cs.font_family, cs.font_size, cs.font_weight, cs.color_hex, cs.background_color_hex, cs.background_image, cs.background_repeat, cs.background_position, cs.is_large_font, cs.color_contrast_ratio, dom_text_node.character_count);
    
    cci.dom_text_nodes.push(dom_text_node);
    cci.node_count = cci.dom_text_nodes.length;
    
    this.color_contrast_items.push(cci);
    this.length = this.length + 1;
    cci.cache_id = "cc_" + this.length;
  }
};

/**
 * @method getColorContrastItemById
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Returns the color contrast item with the cache id
 *
 * @param {String}  cache_id  - cache id of the color contrast item
 *
 * @return {ColorContrastItem} Returns color contrst item if cache id found, otherwise null  
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.getColorContrastItemById = function (cache_id) {
  this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Returns the color contrast item with the cache id
 *
 * @param {String}  cache_id  - cache id of the color contrast item
 *
 * @return {ColorContrastItem} Returns color contrst item if cache id found, otherwise null  
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.getItemByCacheId = function (cache_id) {
  
  var i, j;
  var cci, dtn;
  var dom_text_nodes, dom_text_nodes_len;

  var color_contrast_items     = this.color_contrast_items;
  var color_contrast_items_len = color_contrast_items.length;
  
  if (cache_id && cache_id.length) {
  
    for (i = 0; i < color_contrast_items_len; i++) {
    
      cci = color_contrast_items[i];
      
      if (this.color_contrast_items[i].cache_id == cache_id) {
        return this.color_contrast_items[i];
      }

      dom_text_nodes     = cci.dom_text_nodes;
      dom_text_nodes_len = dom_text_nodes.length;
      
      for (j = 0; j < dom_text_nodes_len; j++ ) {
        dtn = dom_text_nodes[j];
        if (dtn.cache_id == cache_id) return dtn;
      } // end loop

    } // end loop
  }
  
  return null;  
};


/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Empties all the color contrast items from the cache 
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.emptyCache = function () {
  
  this.color_contrast_items.length = 0;
  this.up_to_date = false;
};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Updates the ColorContrastCache object with information from a DOMElement object
 *    This is used during the creation of the cache and is used by the functions for
 *    either creating the cache all at one time or selectively
 *
 * @param {DOMText}  dom_text  - DOM text  object to add to the color contrast cache
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.updateCacheItems = function (dom_text_node) {

  var tn;

  if (dom_text_node.parent_element) {
    tn =  dom_text_node.parent_element.tag_name;
    
    if (tn != 'script' && tn != 'object' && tn != 'style') {
      this.addColorContrastItem(dom_text_node);
    }
  }  
    
};

/**
 * @method traverseDOMElementsForColorContrast
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Traverses the DOMElements to update the color contrast cache
 */
 
OpenAjax.a11y.cache.ColorContrastCache.prototype.traverseDOMElementsForColorContrast = function (dom_element) {
  
  if (! dom_element) return;
  
  if (dom_element.type == Node.ELEMENT_NODE) {
    
    for (var i = 0; i < dom_element.child_dom_elements.length; i++) {
      this.traverseDOMElementsForColorContrast(dom_element.child_dom_elements[i]);
    }
    // end loop
  }
  else {
    this.updateCacheItems(dom_element);
  }  
  
};

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Traverses the DOMElements to update the color contrast cache
 *    This function is used to update the color contrast cache
 *    when needed by a rule, it sets the up to date flag when done
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.updateCache = function () {
  var i;
  var children = this.dom_cache.element_cache.child_dom_elements;
  var children_len = children.length;
  
  for (i = 0; i < children_len; i++) {
    this.traverseDOMElementsForColorContrast(children[i]);
  }
  
  this.up_to_date = true;
};

/**
 * @method sortCCRItems
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Sorts abbreviations by color contrast cache by color contrast ratio property
 *
 * @param {Boolean}  ascending  - true if sort in ascending order; false in descending order
 *
 * @return {Boolean}  Returns true if list was sorted, false if not
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.sortCCRItems = function (ascending) {
  
  var swapped = false;
  var temp = null;
  var i;
  
  if (! this.color_contrast_items || (this.color_contrast_items.length === 0)) {
    return false;
  }
  // endif
  
  this.sort_ascending = ascending;
  
  var color_contrast_items_len = this.color_contrast_items.length;
  
  if (ascending) {
    do {
      swapped = false;
      for (i = 1; i < color_contrast_items_len; i++) {
        if (parseInt(this.color_contrast_items[i - 1].color_contrast_ratio, 10) > parseInt(this.color_contrast_items[i].color_contrast_ratio, 10)) {
          // swap the values
          temp = this.color_contrast_items[i - 1];
          this.color_contrast_items[i - 1] = this.color_contrast_items[i];
          this.color_contrast_items[i] = temp;
          swapped = true;
        }
      }
      // end loop
    }
    while (swapped);
  } else {
    do {
      swapped = false;
      for (i = 1; i < color_contrast_items_len; i++) {
        if (parseInt(this.color_contrast_items[i - 1].color_contrast_ratio, 10) < parseInt(this.color_contrast_items[i].color_contrast_ratio, 10)) {
          // swap the values
          temp = this.color_contrast_items[i - 1];
          this.color_contrast_items[i - 1] = this.color_contrast_items[i];
          this.color_contrast_items[i] = temp;
          swapped = true;
        }
      }
      // end loop
    }
    while (swapped);
  }
  
  this.sort_property = 'color_contrast_ratio';
  
  return true;
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastCache
 *
 * @desc Returns a text string representation of the color contrast cache object 
 *
 * @return {String} Returns string represention the color contrast cache object
 */

OpenAjax.a11y.cache.ColorContrastCache.prototype.toString = function () {
  
  var i;
  
  var item;
  
  var str = "\n\nColor Contrast List Information\n";
  
  var list_length = this.color_contrast_items.length;
  
  for (i = 0; i < list_length; i++) {
    str += this.color_contrast_items[i].toString();
  }
  // end loop
  
  return str;
};

/* ---------------------------------------------------------------- */
/*                      ColorContrastItem                           */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor ColorContrastItem
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructor for ColorContrastItem object which contains information
 *       about dom elements that share the same abbreviation 
 * 
 * @param  {String}  font_family           - Value of the CSS font family property
 * @param  {String}  font_size             - Value of the CSS font size property
 * @param  {String}  font_weight           - Value of the CSS font weight property
 * @param  {String}  color                 - Value of the CSS font color property
 * @param  {String}  background_color      - Value of the CSS background color property
 * @param  {String}  background_image      - Value of the CSS background image property
 * @param  {String}  background_repeat     - Value of the CSS background repeat property
 * @param  {String}  background_position   - Value of the CSS background position property
 * @param  {String}  color_contrast_ratio  - Calculated color contrast ratio
 * @param  {String}  count                 - Initial number of characters
 *         
 * @property  {String}  cache_id  - String that uniquely identifies the cache element in the DOMCache
 *
 * @property  {String}  font_family           - Value of the CSS font family property
 * @property  {String}  font_size             - Value of the CSS font size property
 * @property  {String}  font_weight           - Value of the CSS font weight property
 * @property  {String}  color                 - Value of the CSS font color property
 * @property  {String}  background_color      - Value of the CSS background color property
 * @property  {String}  background_image      - Value of the CSS background image property
 * @property  {String}  background_repeat     - Value of the CSS background repeat property
 * @property  {String}  background_position   - Value of the CSS background position property
 * @property  {String}  color_contrast_ratio  - Calculated color contrast ratio
 * @property  {String}  character_count       - Number of characters in the document that share these color contrast properties
 *  
 * @property  {Boolean}  is_large_font  - true if font is considered large, otherwise false 
 *  
 * @property  {String}   dom_elements - List of dom elements with the same color contrast item properties
 */

OpenAjax.a11y.cache.ColorContrastItem = function (font_family, font_size, font_weight, color, bg_color, bg_image, bg_repeat, bg_position, is_large_font, ccr, count) {

  this.cache_id = "";

  this.font_family          = font_family;
  this.font_size            = font_size;
  this.font_weight          = font_weight;
  this.color                = color;
  this.background_color     = bg_color;
  this.background_image     = bg_image;
  this.background_repeat    = bg_repeat;
  this.background_position  = bg_position;
  
  this.color_contrast_ratio = ccr;
  this.character_count = count;
  
  this.is_large_font = is_large_font;
  
  this.dom_text_nodes = [];
};

/**
 * @member addToCharacterCount
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Add to the total number of characters in the document that matches
 *       the properties of this color contrast item
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.addToCharacterCount = function (length) {
  
  this.character_count += length;
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.getNodeResults = function () {
  return this.dom_text_nodes[0].getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.getStyle = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
  
  cache_nls.addPropertyIfDefined(properties, this, 'is_large_font');
  cache_nls.addPropertyIfDefined(properties, this, 'color_contrast_ratio');
 
  cache_nls.addPropertyIfDefined(properties, this, 'color');
  cache_nls.addPropertyIfDefined(properties, this, 'background_color');
  cache_nls.addPropertyIfDefined(properties, this, 'background_image');
  cache_nls.addPropertyIfDefined(properties, this, 'background_repeat');
  cache_nls.addPropertyIfDefined(properties, this, 'background_position');

  cache_nls.addPropertyIfDefined(properties, this, 'font_family');
  cache_nls.addPropertyIfDefined(properties, this, 'font_size');
  cache_nls.addPropertyIfDefined(properties, this, 'font_weight');  
  
  return properties;
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.getAttributes = function (unsorted) {

  return [];
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
  
  cache_nls.addPropertyIfDefined(properties, this, 'character_count');
  
  return properties;
  
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return null;
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.getEvents = function () {
   
  return [];
  
};


/**
 * @member toString
 *
 * @memberOf OpenAjax.a11y.cache.ColorContrastItem
 *
 * @desc Returns a text string representation of the color contrast item object 
 *
 * @return {String} Returns string represention the color contrast item object
 */

OpenAjax.a11y.cache.ColorContrastItem.prototype.toString = function () {
  
  var str = this.dom_text_nodes.length;
  
  if (this.dom_text_nodes.length != 1) str += " elements"; 
  else str += " element";
    
  return str;
};
/*
 * Copyright 2011, 2012 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                       ControlInfo                                */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor ControlInfo
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a ControlInfo object for preserving the current control information 
 *        when traversing the DOM for form control information
 *
 * @param {ControlInfo} control_info - Current ControlInfo object
 *
 * @property {ControlElement}   control_element  - Parent ControlElement (if any)
 * @property {FieldsetElement}  fieldset_element - Parent FieldsetElement (if any)
 * @property {SelectElement}    select_element   - Parent SelectElement (if any)
 * @property {LabelElement}     label_element    - Parent LabelElement (if any)
 * @property {FormElement}      form_element     - Parent FormElement (if any)
 */

OpenAjax.a11y.cache.ControlInfo = function (control_info) {
 
 if (control_info) {
  this.control_element  = control_info.control_element;
  this.fieldset_element = control_info.fieldset_element;
  this.select_element   = control_info.select_element;
  this.label_element    = control_info.label_element; 
  this.form_element     = control_info.form_element; 
  this.parent_widget    = control_info.parent_widget; 
 }
 else {
  this.control_element  = null;
  this.fieldset_element = null;
  this.select_element   = null;
  this.label_element    = null;
  this.form_element     = null;
  this.parent_widget    = null; 
 } 
}; 

/* ---------------------------------------------------------------- */
/*                       ControlsCache                              */ 
/* ---------------------------------------------------------------- */

/** 
 * @constructor ControlsCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc ControlsCache is the constructor for lists of control cache element objects and
 *       the root of a tree representation of the control cache element relationships
 *       
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                          based on whether a rule needs the cache
 *
 * @property {Array}    child_cache_elements  - Root array of the tree representation of the controls in the document 
 *
 * @property {Array}    control_elements      - List of all the InputElement, TextareaElement, ButtonElement, SelectElement, 
 *                                              OptionElements and OptgroupElement objects in the cache
 *
 * @property {Number}   control_length        - Length of the control_elements array and used in calculating cache IDs
 *
 * @property {Array}    label_elements        - List of all the LabelElement objects in the cache
 * @property {Number}   label_length          - Length of the label_elements array and used in calculating cache IDs
 *
 * @property {Array}    fieldset_elements     - List of all the FieldsetElement objects in the cache
 * @property {Number}   fieldset_length       - Length of the Fireldset_elements array and used in calculating cache IDs
 *
 * @property {Array}    form_elements         - List of all the FormElement objects in the cache
 * @property {Number}   form_length           - Length of the form_elements array and used in calculating cache IDs
 *
 * @property {String}   sort_property         - The property the list of control element object is currenlty sorted by
 * @property {Boolean}  ascending             - true if the list is ascending order or false if descending
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this
 */

OpenAjax.a11y.cache.ControlsCache = function (dom_cache) {

  this.dom_cache     = dom_cache;
  this.up_to_date    = false;
 
  this.child_cache_elements  = [];
 
  this.control_elements = [];
  this.control_length  = 0;

  this.widget_elements = [];
  this.widget_length  = 0;
  
  this.elements_with_role = [];
  this.elements_with_aria_attributes = [];

  this.elements_with_events = [];

  this.label_elements  = [];
  this.label_length   = 0;
  
  this.fieldset_elements = [];
  this.fieldset_length = 0;
  
  this.form_elements   = [];
  this.form_length   = 0;

  this.sort_property  = 'document_order';
  this.ascending    = true;
 
};

/**
 * @method addChildControl
 * 
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 * 
 * @desc Adds a cache control element to the root tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.ControlsCache.prototype.addChildControl = function (control_element) {

  if (control_element) {
    this.child_cache_elements.push(control_element); 
  }  
   
}; 

/** 
 * @method addControlElement
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Adds a cache control element to the list of controls array and generates a cache_id for each control 
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 *
 * @return  {Number} Returns the number of control objects in the control_elements array
 */

OpenAjax.a11y.cache.ControlsCache.prototype.addControlElement = function (control_element) {

//  OpenAjax.a11y.logger.debug("  Adding control element: " + control_element.dom_element.tag_name + " ("+ control_element.control_type + ")");

  // item must exist and have the position property
  if (control_element) {
    this.control_length += 1;
    control_element.document_order = this.control_length;
    control_element.cache_id = "control_" + this.control_length;
    this.control_elements.push( control_element );
    return true;
  } 

  return this.control_length;

};

/**
 * @method addLabelElement
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Add LabelElement object to list of label elements and generates a cache id for the object
 *
 * @param  {LabelElement} label_element  - LabelElement object to add 
 *
 * @return  Nothing 
 */

OpenAjax.a11y.cache.ControlsCache.prototype.addLabelElement = function (label_element) {

  if (label_element) {
    this.label_length += 1;
    label_element.document_order = this.label_length;
    label_element.cache_id = "label_" + this.label_length;
    this.label_elements.push( label_element );
  } 

};

/**
 * @method addFormElement
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Add a FormElement object to the list of form elements and generates a cache id for the object 
 *
 * @param  {FormElement} form_element  - FormElement to add 
 *
 * @return {Number} Returns number of FormElement objects in the list of form elements
 */

OpenAjax.a11y.cache.ControlsCache.prototype.addFormElement = function (form_element) {

  // item must exist and have the position property
  if (form_element) {
    this.form_length = this.form_length + 1;
    form_element.document_order = this.form_length;
    form_element.cache_id = "form_" + this.form_length;
    this.form_elements.push( form_element );
  } 

  return this.form_length;

};

/** 
 * @method addFieldsetElement
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Adds a FieldsetElement to the list of fieldset elements and generates a cache id for the object 
 *
 * @param  {FieldsetElement}  fieldset_element  - FieldsetElement to add 
 *
 * @return {Number} Returns the number of FieldsetElement objects in the list of fieldset elements
 */

OpenAjax.a11y.cache.ControlsCache.prototype.addFieldsetElement = function (fieldset_element) {

  // item must exist and have the position property
  if (fieldset_element) {
    this.fieldset_length = this.fieldset_length + 1;
    fieldset_element.document_order = this.fieldset_length;
    fieldset_element.cache_id = "fieldset_" + this.fieldset_length;
    this.fieldset_elements.push(fieldset_element);
  } 

  return this.fieldset_length;

};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Resests the ControlsCache object properties and empties all the lists and arrays 
 */

OpenAjax.a11y.cache.ControlsCache.prototype.emptyCache = function () {

  this.up_to_date    = false;
 
  this.child_cache_elements  = [];
 
  this.control_elements = [];
  this.control_length  = 0;
  
  this.label_elements  = [];
  this.label_length   = 0;
  
  this.fieldset_elements = [];
  this.fieldset_length = 0;
  
  this.form_elements   = [];
  this.form_length   = 0;

  this.sort_property  = 'document_order';
  this.ascending    = true;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Updates the ControlsCache object by checking to see if a DOMElement
 *          should be added to the control cache objects
 *  
 * @param  {DOMElement}   dom_element   - DOMElement object to check for inclusion in controls cache
 * @param  {ControlInfo}  control_info  - Information about the current control relationships in the DOM
 *
 * @return {ControlInfo}  Returns updated control info object 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.updateCacheItems = function (dom_element, control_info) {
 
  var be;
  var fe;
  var ie;
  var le;
  var oe;
  var se;
  var te;  
  var we;
  
  var ci = new OpenAjax.a11y.cache.ControlInfo(control_info);

  // check for widget
  
  if (dom_element.hasEvents()) this.elements_with_events.push(dom_element);
  
  if (dom_element.has_role) this.elements_with_role.push(dom_element);
  
  if (dom_element.has_aria_attributes) {

    // OpenAjax.a11y.logger.debug(dom_element + " has aria attributes");
    this.elements_with_aria_attributes.push(dom_element);
  }  
 
  if (dom_element.is_widget) {
    
    we = new OpenAjax.a11y.cache.WidgetElement(dom_element, control_info);
    this.addLabel(we, "", OpenAjax.a11y.SOURCE.NONE);
    
    this.addControlElement(we);
    this.widget_elements.push(we);
    
    if (control_info.control_element) {
      control_info.control_element.addChildControl(we);   
    }
    else {
      this.addChildControl(we);     
    }
  
    ci.control_element = we;
    
    if (!we.has_owns) ci.parent_widget = we;
  
  }
  else {

    switch (dom_element.tag_name) {

    case 'form':
      fe = new OpenAjax.a11y.cache.FormElement(dom_element, control_info);

      this.addFormElement(fe); 

      if (control_info.control_element) {
        control_info.control_element.addChildControl(fe);   
      }
      else {
        this.addChildControl(fe);     
      }
  
      ci.control_element = fe;
      ci.form_element = fe;
  
      break;

    case 'fieldset':
      fe = new OpenAjax.a11y.cache.FieldsetElement(dom_element, control_info);
    
      this.addFieldsetElement(fe); 
  
      if (control_info.control_element) {
        control_info.control_element.addChildControl(fe);   
      }
      else {
        this.addChildControl(fe);     
      }
  
      ci.control_element = fe;
      ci.fieldset_element = fe;
      break;

    case 'legend':
      le = new OpenAjax.a11y.cache.LegendElement(dom_element, control_info);
      le.computed_label = this.getElementTextContent(le, false);
      le.computed_label_length = le.computed_label.length;

      this.addLabelElement(le); 
  
      if (control_info.control_element) {
        control_info.control_element.addChildControl(le);   
      }
      else {
        this.addChildControl(le);     
      }

      if (control_info.fieldset_element) {
        control_info.fieldset_element.legend_element = le;
      }

      ci.control_element = le;
      break;

    case 'label':
      le = new OpenAjax.a11y.cache.LabelElement(dom_element, control_info);
      le.computed_label = this.getElementTextContent(le, false);
      le.computed_label_length = le.computed_label.length;
    
      this.addLabelElement(le); 
  
      if (control_info.control_element) {
        control_info.control_element.addChildControl(le);   
      }
      else {
        this.addChildControl(le);     
      }
    
      ci.control_element = le;
      ci.label_element  = le;
      break;

    case 'input':
      ie = new OpenAjax.a11y.cache.InputElement(dom_element, control_info);
      this.addLabel(ie, "", OpenAjax.a11y.SOURCE.NONE);
      
      if (ie.dom_element.node.type.toLowerCase() != "hidden") {
    
        this.addControlElement(ie); 
  
        if (control_info.control_element) {
          control_info.control_element.addChildControl(ie);   
        }
        else {
          this.addChildControl(ie);     
        }
      
        if (control_info.form_element) {
          control_info.form_element.number_of_controls += 1;   
        }
      
        if (control_info.fieldset_element) {
          control_info.fieldset_element.number_of_controls += 1;   
        }
      } 
  
      break;

    case 'button':
      be = new OpenAjax.a11y.cache.ButtonElement(dom_element, control_info);
      this.addLabel(be, "", OpenAjax.a11y.SOURCE.NONE);

      this.addControlElement(be); 

      if (control_info.control_element) {
        control_info.control_element.addChildControl(be);   
      }
      else {
        this.addChildControl(be);     
      }

      if (control_info.form_element) {
        control_info.form_element.number_of_controls += 1;   
      }

      if (control_info.fieldset_element) {
        control_info.fieldset_element.number_of_controls += 1;   
      }
    
      ci.control_element = be;
      break;

    case 'textarea':
      te = new OpenAjax.a11y.cache.TextareaElement(dom_element, control_info);
      this.addLabel(te, "", OpenAjax.a11y.SOURCE.NONE);

      this.addControlElement(te); 

      if (control_info.control_element) {
        control_info.control_element.addChildControl(te);   
      }
      else {
        this.addChildControl(te);     
      }
    
      if (control_info.form_element) {
        control_info.form_element.number_of_controls += 1;   
      }

      if (control_info.fieldset_element) {
        control_info.fieldset_element.number_of_controls += 1;   
      }
    
      break;

    case 'select':
      se = new OpenAjax.a11y.cache.SelectElement(dom_element, control_info);
      this.addLabel(se, "", OpenAjax.a11y.SOURCE.NONE);
  
      this.addControlElement(se); 
  
      if (control_info.control_element) {
        control_info.control_element.addChildControl(se);   
      }
      else {
        this.addChildControl(se);     
      }
    
      if (control_info.form_element) {
        control_info.form_element.number_of_controls += 1;   
      }
  
      if (control_info.fieldset_element) {
        control_info.fieldset_element.number_of_controls += 1;   
      }
    
      ci.select_element = se;
      ci.control_element = se;
      break;

    case 'optgroup':
      oe = new OpenAjax.a11y.cache.OptgroupElement(dom_element, control_info);
  
      if (dom_element.node.label && dom_element.node.label.length) {
        oe.label = dom_element.node.label;  
        oe.label_length = oe.label.length;
      } 
 
      if (control_info.control_element) {
       control_info.control_element.addChildControl(oe);   
      }
      else {
        this.addChildControl(oe);     
      }
 
      ci.control_element = oe;
      break;

    case 'option':
      oe = new OpenAjax.a11y.cache.OptionElement(dom_element, control_info);
  
      oe.computed_label = this.getElementTextContent(oe, false);
      oe.computed_label_length = oe.computed_label.length;

  
      if (control_info.control_element) {
        control_info.control_element.addChildControl(oe);   
      }
      else {
        this.addChildControl(oe);     
      }

      if (control_info.select_element) {
        control_info.select_element.addOption(oe);   
      }

      break;

    default:
  
      break;

    } // end switch
    
    // if we are in a widget there a few HTML elements with implied roles
    
    if (control_info.parent_widget) {
    
      var implied_role = null;
      var role = control_info.parent_widget.dom_element.role;
      var tag_name = dom_element.tag_name;
    
      if (tag_name === 'tr' && (" grid rowgroup treegrid".indexOf(role) > 0)) {
        implied_role = 'row';        
      }

      if ((tag_name === 'td' || tag_name === 'th') && (" grid rowgroup treegrid".indexOf(role) > 0)) {

        var scope = dom_element.node.getAttribute('scope');

        if (typeof scope === 'string') {
          scope = scope.toLowerCase();
          if (scope === 'col') implied_role = 'columnheader';        
          else if (scope === 'row') implied_role = 'rowheader';   
        }  
      }

      if ((tag_name === 'thead' || tag_name === 'tfoot' || tag_name === 'tbody') && role === 'grid') {
        implied_role = 'rowgroup';              
      }

      if (implied_role && (implied_role.length > 0)) {
      
//        OpenAjax.a11y.logger.debug("  Adding implied role: " + implied_role);
        
        dom_element.setImpliedRole(implied_role);

        we = new OpenAjax.a11y.cache.WidgetElement(dom_element, control_info);
        this.addLabel(we, "", OpenAjax.a11y.SOURCE.NONE);
    
        this.addControlElement(we);
        this.widget_elements.push(we);
    
        if (control_info.control_element) {
          control_info.control_element.addChildControl(we);   
        }
        else {
          this.addChildControl(we);     
        }
        
        ci.control_element = we;
        if (!we.has_owns) ci.parent_widget = we;

      }
  
    }
    
  }   

  return ci;
};

/**
 * @method traverseDOMElementsForControlElements
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Traverses DOMElement objects in the tree to update the controls cache 
 *
 * @param  {DOMElement}  dom_element   - DOMElement object to check for inclusion in controls cache
 * @param  {ControlInfo} control_info  - Current control information object that contains information 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.traverseDOMElementsForControlElements = function (dom_element, control_info) {
 
  var i;
  var ci;

  if (!dom_element) return;
 
  if (dom_element.type == Node.ELEMENT_NODE) {

    ci = this.updateCacheItems(dom_element, control_info);
  
    for (i = 0; i < dom_element.child_dom_elements.length; i++ ) {
      this.traverseDOMElementsForFormElements(dom_element.child_dom_elements[i], ci);
    } // end loop
  
  }  
}; 

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Traverses the DOMElements to update the controls cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the controls cache are disabled, this cache would 
 *       not be updated
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.updateCache = function () {
 var i;
 var children = this.dom_cache.element_cache.child_dom_elements;
 var children_len = children.length;
 
 var control_info = new OpenAjax.a11y.cache.ControlInfo(null);
  
 for (i=0; i < children_len; i++) {
  this.traverseDOMElementsForControlElements(children[i], control_info);
 }  
 
 this.calculateControlLabels();
 this.applyAriaOwns();
 
 this.up_to_date = true;
};

/**
 * @method getRuleResults
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Returns an array of rule results for the cache items in the controls cache 
 *
 * @param  {Number}  filter  - Filter for returning rules with particular type(s) of   
 *
 * @return {Array} Returns array of rule results, can be empty
 */

OpenAjax.a11y.cache.ControlsCache.prototype.getRuleResults = function (filter) {

  function traverseCacheItems(cache_item) {
  
    var flag = false;
    var de = cache_item.dom_element;
    
    if ((local_filter & RESULT_FILTER.PASS)                  && de.rules_passed.length)        flag = true; 
    if (!flag && (local_filter & RESULT_FILTER.VIOLATION)    && de.rules_violations.length)    flag = true; 
    if (!flag && (local_filter & RESULT_FILTER.WARNING)      && de.rules_warnings.length)      flag = true; 
    if (!flag && (local_filter & RESULT_FILTER.PAGE_MANUAL_CHECK)    && de.rules_manual_checks.length) flag = true; 
    if (!flag && (local_filter & RESULT_FILTER.ELEMENT_MANUAL_CHECK) && de.rules_manual_checks.length) flag = true; 
    if (!flag && (local_filter & RESULT_FILTER.NA)       && de.rules_hidden.length)        flag = true; 
    
    if (flag) cache_items.push(cache_item);

    if (cache_item.child_cache_elements) {
      var child_cache_elements     = cache_item.child_cache_elements;
      var child_cache_elements_len = child_cache_elements.length;
  
      for (var i = 0; i < child_cache_elements_len; i++) {
        var ci = child_cache_elements[i];

        traverseCacheItems(ci);
      }
    }  
  }

  var RESULT_FILTER = OpenAjax.a11y.RESULT_FILTER;

  var local_filter;

  if (!filter) 
    local_filter = RESULT_FILTER.ALL;
  else
    local_filter = filter;

  var rule_results = [];
  
  var child_cache_elements     = this.child_cache_elements;
  var child_cache_elements_len = child_cache_elements.length;
  
  for (var i = 0; i < child_cache_elements_len; i++) {
    var ci = child_cache_elements[i];

    traverseCacheItems(ci);
  
  } 

  return cache_items;

};



/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Finds the the control cache element object with the matching cache id
 *
 * @param  {String }  cache_id  - Cache id of control cache element object
 *
 * @return {cache control element object} Returns cache control element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.ControlsCache.prototype.getItemByCacheId = function (cache_id) {

  var item = null;
  
  item = this.getControlElementByCacheId(cache_id);
  if (item) return item;

  item = this.getLabelElementByCacheId(cache_id);
  if (item) return item;

  item = this.getFormElementByCacheId(cache_id);
  if (item) return item;

  item = this.getFieldsetElementByCacheId(cache_id);
  
  return item;

};

/**
 * @method getControlElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Finds the the control cache element object with the matching cache id
 *
 * @param  {String }  cache_id  - Cache id of control cache element object
 *
 * @return {cache control element object} Returns cache control element object if cache id is found, otherwise null
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.getControlElementByCacheId = function (cache_id) {

  for (var i = 0; i<this.control_elements.length; i++) {
    if (this.control_elements[i].cache_id == cache_id) return this.control_elements[i];
  }

  return null;
};

/**
 * @method getControlElementById
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Finds the the control cache element object with the matching id
 *
 * @param  {String }  id  - id of control cache element object
 *
 * @return {cache control element object} Returns cache control element object if cache id is found, otherwise null
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.getControlElementById = function (id) {

  for (var i = 0; i < this.control_elements.length; i++) {
    if (this.control_elements[i].dom_element.id == id) {
      return this.control_elements[i];
    }
  }

  return null;
};

/**
 * @method getLabelElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Finds the the LabelElement object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of LabelElement object
 *
 * @return {LabelElement}  Returns label element with the cache id if found, otherwise null
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.getLabelElementByCacheId = function (cache_id) {

  for (var i = 0; i < this.label_elements.length; i++) {
    if (this.label_elements[i].cache_id == cache_id) return this.label_elements[i];
  }

  return null;
};

/**
 * @method getFormElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Finds the the FormElement object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of FormElement object
 *
 * @return {FormElement}  Returns form element with the cache id if found, otherwise null
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.getFormElementByCacheId = function (cache_id) {

 var i;

 for (i=0; i<this.form_elements.length; i++) {
  if (this.form_elements[i].cache_id == cache_id) {
   return this.form_elements[i];
  }
 }

 return null;
};

/**
 * @method getFieldsetElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Finds the the FieldsetElement object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of FieldsetElement object
 *
 * @return {FieldsetElement}  Returns fieldset element with the cache id if found, otherwise null
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.getFieldsetElementByCacheId = function (cache_id) {

 var i;

 for (i=0; i<this.fieldset_elements.length; i++) {
  if (this.fieldset_elements[i].cache_id == cache_id) {
   return this.fieldset_elements[i];
  }
 }

 return null;
};

/**
 * @method getElementTextContent
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Traverses the cache to get the text content associated with the label, this will include the 
 *       values of form controls in the label references
 *
 * @param  {LabelElement}  label_element           - LabelElement object to calculate the text content
 * @param  {Boolean}       include_control_values  - True if the values of form controls should be included in 
 *                                                   accessible name calculation
 *
 * @return {String}  Returns the text content of a LabelElement
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.getElementTextContent = function (label_element, include_control_values) {

 var strings = [];
 
 function getText(dom_element) {
  var i;
  
  // If text node get the text and return
  if( dom_element.type == Node.TEXT_NODE ) {
   var text = dom_element.text;
   strings.push( text );
  } else {
   // if an element for through all the children elements looking for text
   if( dom_element.type == Node.ELEMENT_NODE ) {
   
    switch (dom_element.tag_name) {

    case 'img':
    case 'area':
     strings.push( dom_element.alt );     
     break;
     
    case 'input':
     if (include_control_values && dom_element.node.type == 'text') strings.push(dom_element.node.value);
     break;       

    case 'select':
     // *** need to add some code here to get 
     break;       
     
    default:
     break;    

    } // end switch     
    
    for (i = 0; i < dom_element.child_dom_elements.length; i++ ) {
     getText( dom_element.child_dom_elements[i]);
    }      
    
   }  
  } 
 } // end function getText

 getText(label_element.dom_element); 
 
 return strings.join("").normalizeSpace();
 
};

/**
 * @method calculateLabelsUsingARIA
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Interates the array for control cache elements and calculates the accessible name for
 *         any control elements if there is ARIA markup 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.calculateLabelsUsingARIA = function () {

  var control_elements     = this.control_elements;
  var control_elements_len = control_elements.length;
  
  // first check if an label by reference
 
  for (var i = 0; i < control_elements_len; i++) {
 
    var ce = control_elements[i];
    var de = ce.dom_element;
    
    if ( (de.aria_labelledby && de.aria_labelledby.length) || 
         (de.aria_label && de.aria_label.length) ||
         (de.role_info)) {
         
      this.dom_cache.getNameFromARIALabel(ce);
      
      // If title attribute is the result clear label for use of other labeling techniques
      if (ce.computed_label_source == OpenAjax.a11y.SOURCE.TITLE_ATTRIBUTE && !ce.role_info) {
        ce.computed_label = "";
        this.addLabel(ce, "", OpenAjax.a11y.SOURCE.NONE);
      }
    }
  }
};


/**
 * @method addFieldsetLegend
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Adds legend content to computed label if control is contained in a fieldset/legend
 *
 * @param {Object}  control  -  Control Object
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.addFieldsetLegend = function (control) {

   // Add fieldset/legend information if defined
   if (control.fieldset_element && 
       control.fieldset_element.legend_element) {
       control.computed_label = control.fieldset_element.legend_element.computed_label + " ";
       control.computed_label_length = control.computed_label.length;
   }
   
};

/**
 * @method addLabel
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Adds legend content to computed label if control is contained in a fieldset/legend
 *
 * @param {Object}  control -  Control Object
 * @param {String}  label   -  label text
 * @param {Number}  source  -  label source
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.addLabel = function (control, label, source) {

  if (source === OpenAjax.a11y.SOURCE.NONE) {
    control.computed_label  = "";
    if (control.dom_element.role_info) control.accessible_name = "";
  } else {
    this.addFieldsetLegend(control);
    control.computed_label += label + " ";
  }
  
  control.computed_label_length = control.computed_label.length;
  control.computed_label_source = source;
  control.computed_label_for_comparison = control.computed_label.normalizeSpace().toLowerCase();
   
};


/**
 * @method calculateLabelsByReference
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Iterates the list of label elements and calculates the accessible label for
 *       any control elements that are referenced by label elements with for attribute
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.calculateLabelsByReference = function () {

  var SOURCE = OpenAjax.a11y.SOURCE;

  var label_elements      = this.label_elements;
  var label_elements_len = label_elements.length;
  
  // first check if an label by reference
 
  for (var i = 0; i < label_elements_len; i++) {
 
    var le = label_elements[i];
 
    var id;
    if (le.for_id) {
      id = le.for_id;
    }
    else {
      id = null;
    }  

    if (id && id.length && !le.hidden_label) {
    
      var ce = this.getControlElementById(id);
      
      if (ce) {

        // check to see if label defined (i.e. an ARIA technique)

        if ((ce.computed_label_source !== SOURCE.ARIA_LABELLEDBY) && 
            (ce.computed_label_source !== SOURCE.ARIA_LABEL)) {
          this.addLabel(ce, le.computed_label, OpenAjax.a11y.SOURCE.LABEL_REFERENCE);
          le.unused_label = false;          
          le.control_element = ce;
        }  
      }
      else {
        le.unused_label = true;
      }
      
    }
  }
};

/**
 * @method calculateLabelsByEncapsulation
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Iterates the list of label elements and calculates the accessible label for
 *       any control elements that are encapsulated by a label element
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.calculateLabelsByEncapsulation = function () {

  var control_elements = this.control_elements;
  var control_elements_len = control_elements.length;
  
  for (var i = 0; i < control_elements_len; i++) {
 
    var ce = control_elements[i];
 
    switch (ce.control_type) {
  
    case OpenAjax.a11y.CONTROL_TYPE.BUTTON_ELEMENT:
      this.addLabel(ce, this.getElementTextContent(ce, false), OpenAjax.a11y.SOURCE.TEXT_CONTENT);
      break;
  
    default:
    
      // first check if an label exists

      if (ce.computed_label.length === 0 && ce.label_element) {  
        this.addLabel(ce, ce.label_element.computed_label, OpenAjax.a11y.SOURCE.LABEL_ENCAPSULATION);
        ce.label_element.unused_label = false;
        ce.label_element.control_element = ce;
      }
      break;
    } // end switch 
  } // end loop
};

/**
 * @method calculateLabelsByOther
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Iterates the list of control elements and calculates the 
 *       accessible label for any control elements that do NOT have 
 *       a computed label, but has a VALUE, ALT or TITLE attribute value 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.calculateLabelsByOther = function () {

  var CONTROL_TYPE = OpenAjax.a11y.CONTROL_TYPE;
  
  var control_elements     = this.control_elements;
  var control_elements_len = control_elements.length;
  
  // first check if an label exits
 
  for (var i = 0; i < control_elements_len; i++) {
 
    var ce = control_elements[i];
 
    if (ce.computed_label.length === 0) {
      var de = ce.dom_element;

      switch (ce.control_type) {
  
      case CONTROL_TYPE.BUTTON_INPUT:
        if (ce.value && ce.value.length) {   
          this.addLabel(ce, ce.value, OpenAjax.a11y.SOURCE.VALUE_ATTRIBUTE);        
        }
        else {
          this.addLabel(ce, "", OpenAjax.a11y.SOURCE.NONE);                  
        }
        break;
 
      case CONTROL_TYPE.IMAGE:

        if (de.alt) {
          this.addLabel(ce, de.alt, OpenAjax.a11y.SOURCE.ALT_ATTRIBUTE);        
        }
        else {
          if (de.title && de.title.length) {
            this.addLabel(ce, de.title, OpenAjax.a11y.SOURCE.TITLE_ATTRIBUTE);       
          }
          else {
            this.addLabel(ce, "", OpenAjax.a11y.SOURCE.NONE);      
          }
        }  
        break;

      case CONTROL_TYPE.SUBMIT:

        if (ce.value && ce.value.length) {
          this.addLabel(ce, ce.value, OpenAjax.a11y.SOURCE.VALUE_ATTRIBUTE);        
        }
        else {
          this.addLabel(ce, "SUBMIT", OpenAjax.a11y.SOURCE.BUTTON_TYPE);      
        }  
        break;

      case CONTROL_TYPE.RESET:

        if (ce.value && ce.value.length) {
          this.addLabel(ce, ce.value, OpenAjax.a11y.SOURCE.VALUE_ATTRIBUTE);        
        }
        else {
          this.addLabel(ce, "RESET", OpenAjax.a11y.SOURCE.BUTTON_TYPE);      
        }  
        break;

      default:
    
        if (de.title &&
            de.title.length) {
          // first check if an label exists

          this.addLabel(ce, ce.dom_element.title, OpenAjax.a11y.SOURCE.TITLE_ATTRIBUTE);
        }

        break;
      } // end switch 
    }
  }
};

/**
 * @method calculateControlLabels
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Calculates labels for all form controls, based on the order of label 
 *       calculation techniques used by browsers to generate accessible names
 *       for accessibility APIs used by assistive technologies 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.calculateControlLabels = function () {

  // These functions are called in the order of overrides
  // Once a control has a label it is ignored by subsequent function calls
  this.calculateLabelsUsingARIA();
  this.calculateLabelsByReference();
  this.calculateLabelsByEncapsulation();
  this.calculateLabelsByOther();
};

/**
 * @method applyAriaOwns
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Applies parent/child widget relationships based on the aria-owns property
 *       if aria-owns property is defined for any widgets 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.applyAriaOwns = function () {

  var widgets = this.widget_elements;
  var widgets_len = widgets.length;
  
  for (var i = 0; i < widgets_len; i++) {
  
    var widget = widgets[i];
  
    if (widget.has_owns) {
    
//      OpenAjax.a11y.logger.debug("  Owned: " + widget.cache_id);
    
      var ids = widget.getOwnedIds();
      var ids_len = ids.length;
      
      for (var j = 0; j < ids_len; j++) {
         
         var id = ids[j];
        
         var ce = this.getControlElementById(id);
         
         if (ce) {
         
           this.removeFromChildCacheElements(ce);
           widget.addChildControl(ce, true);
           ce.addOwnerControl(widget);
         }
      }    
    }
  
  }

};


/**
 * @method removeFromChildCacheElements
 *
 * @memberOf OpenAjax.a11y.cache.ControlsCache
 *
 * @desc Removes a control from the tree view of form controls and widgets 
 */
 
OpenAjax.a11y.cache.ControlsCache.prototype.removeFromChildCacheElements = function (item) {

  function removeItem(list) {
  
    for (var i = 0; i < list.length; i++ ) {
    
      if (list[i] === item) {
        list = list.splice(i, 1);  
        return true;
      }

      if (list.child_cach_elements && list.child_cach_elements.lengh) {
         if (removeItem(item)) return true;
      }    
    }
    return false;
  }
  
  removeItem(this.child_cache_elements);

};


/* ---------------------------------------------------------------- */
/*                       FormElement                                */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor FormElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a FormElement object used to hold information about form elements
 *
 * @param  {DOMelement}   dom_element   - dom_element object references DOMElement of the form element 
 * @param  {ControlInfo}  control_info  - Information about the parent control cache
 *
 * @property  {DOMElement}  dom_element           - DOMElement associated with the form element
 * @property  {String}      cache_id              - String that uniquely identifies the cache element in the DOMCache
 * @property  {Number}      document_order        - Ordinal position of the form element in the document in relationship to other form elements
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 * @property  {Number}      number_of_controls    - Number of controls in form
 *
 * @property  {String}  action  - The value of the action attribute of the form control
 * @property  {String}  method  - The value of the method attribute of the form control
 * @property  {String}  name_attribute  - The value of the name attribute of the form control
 */

OpenAjax.a11y.cache.FormElement = function (dom_element, control_info) {

  this.dom_element  = dom_element;
  this.child_cache_elements = [];
  this.cache_id     = "";
  this.document_order = 0;
  
  this.control_type = OpenAjax.a11y.CONTROL_TYPE.FORM;
  this.number_of_controls = 0;
 
  this.action = dom_element.node.action;
  this.method = dom_element.node.method;
  
  this.name_attribute   = dom_element.node.name;
         
};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.FormElement.prototype.addChildControl = function (child_control) {

  if (child_control) {
   this.child_cache_elements.push(child_control); 
  }  
}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.FormElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.FormElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};


/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.FormElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  cache_nls.addPropertyIfDefined(attributes, this, 'row_span');
  cache_nls.addPropertyIfDefined(attributes, this, 'column_span');
  cache_nls.addPropertyIfDefined(attributes, this, 'headers');
  cache_nls.addPropertyIfDefined(attributes, this, 'scope');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.FormElement.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = this.dom_element.getCacheProperties();
  
  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.FormElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.FormElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.FormElement
 *
 * @desc Returns a text string representation of the FormElement 
 *
 * @return {String} Returns string represention the FormElement object
 */
 
OpenAjax.a11y.cache.FormElement.prototype.toString = function () {
  return "form: " + this.number_of_controls + " controls"; 
};

/* ---------------------------------------------------------------- */
/*                       FieldsetElement                            */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor FieldsetElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a FieldsetElement object used to hold information about fieldset elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the fieldset element 
 * @param  {ControlInfo}  control_info  - Information about parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the fieldset element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the fieldset element in the document in relationship to other fieldset elements
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 * @property  {Number}      number_of_controls    - Number of controls in form
 *
 * @property  {FieldsetElement}  fieldset_element  - Reference to any fieldset elements this fieldset is nested in
 * @property  {LegendElement}    legend_element    - Reference to the legend element contained in the fieldset 
 * @property  {Number}           legend_count      - Number of legend elements contained in the fieldset
 */

OpenAjax.a11y.cache.FieldsetElement = function (dom_element, control_info) {

  this.dom_element    = dom_element;
  this.cache_id       = "";
  this.document_order = 0;
  
  this.child_cache_elements = [];
  this.control_type = OpenAjax.a11y.CONTROL_TYPE.FIELDSET;
  this.number_of_controls = 0;   
 
  this.fieldset_element = control_info.fieldset_element;
 
  this.legend_element = null;
 
  this.legend_count = 0;
         
};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.addChildControl = function (child_control) {

  if (child_control) {
    this.child_cache_elements.push(child_control); 
  }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

//  cache_nls.addPropertyIfDefined(properties, this, 'tag_name');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.FieldsetElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.FieldsetElement
 *
 * @desc Returns a text string representation of the fieldset element 
 *
 * @return {String} Returns string represention the FieldsetElement object
 */
 
OpenAjax.a11y.cache.FieldsetElement.prototype.toString = function () {
 if (this.legend_element)  
   return "fieldset: has legend";
 else
   return "fieldset: no legend"; 
     
};

/* ---------------------------------------------------------------- */
/*                       LegendElement                              */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor LegendElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a LegendElement object used to hold information about legend elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the legend element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the legend element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the legend element in the document in relationship to other legend elements
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 *
 * @property  {FieldsetElement}  fieldset_element     - Reference to any fieldset elements this legend is nested in
 * @property  {String}           computed_label                - Text content of the legend element 
 * @property  {String}           computed_label_for_comparison - Label for comparison (lowercase, space normalization and trimmed)
 */

OpenAjax.a11y.cache.LegendElement = function (dom_element, control_info) {

  this.dom_element  = dom_element;
  this.cache_id     = "";
  this.document_order = 0;
  
  this.child_cache_elements = [];
  this.control_type = OpenAjax.a11y.CONTROL_TYPE.LEGEND;
 
  this.fieldset_element = control_info.fieldset_element;
  
  this.computed_label = "";
  this.computed_label_length = 0;
  this.computed_label_for_comparison = "";

  if (control_info.fieldset_element) {
    control_info.fieldset_element.legend_count++;
  }

};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.LegendElement.prototype.addChildControl = function (child_control) {

 if (child_control) {
  this.child_cache_elements.push(child_control); 
 }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.LegendElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.LegendElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.LegendElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.LegendElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

//  cache_nls.addPropertyIfDefined(properties, this, 'tag_name');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.LegendElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};



/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.LegendElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};
/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.LegendElement
 *
 * @desc Returns a text string representation of the legend element 
 *
 * @return {String} Returns string represention the LegendElement object
 */
 
OpenAjax.a11y.cache.LegendElement.prototype.toString = function () {
 if (this.computed_label.length) 
   return "legend: " + this.computed_label; 
 else
   return "legend: empty"; 
 
};

/* ---------------------------------------------------------------- */
/*                       LabelElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor LabelElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a LabelElement object used to hold information about label elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the label element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the label element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the label element in the document in relationship to other label elements
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 *
 * @property  {String}      computed_label                 - Text content of the label element 
 * @property  {Number}      computed_label_len             - Length of the computed label  
 * @property  {String}      computed_label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 *
 * @property  {Boolean}     unused_label                   - Boolean indicting where the label references a form control 
 * @property  {Object}      control_element    - Reference to the control that the label elements is associated with  
 *
 * @property  {FieldsetElement}  fieldset_element     - Reference to any fieldset elements this label is nested in
 */

OpenAjax.a11y.cache.LabelElement = function (dom_element, control_info) {

 this.dom_element    = dom_element;
 this.cache_id       = "";
 this.document_order = 0;
 
 this.child_cache_elements = [];
 
 this.control_type = OpenAjax.a11y.CONTROL_TYPE.LABEL;

 this.computed_label = "";
 this.computed_label_length = 0;
 this.computed_label_for_comparison = "";

 this.unused_label =  true;
 this.hidden_label = (dom_element.computed_style.is_visible_to_at === OpenAjax.a11y.VISIBILITY.HIDDEN);
 this.control_element =  null;

 this.fieldset_element = control_info.fieldset_element;

 this.for_id = dom_element.node.getAttribute('for');
         
};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.LabelElement.prototype.addChildControl = function (child_control) {

 if (child_control) {
  this.child_cache_elements.push(child_control); 
 }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.LabelElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.LabelElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.LabelElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  cache_nls.addPropertyIfDefined(attributes, this, 'for_id');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.LabelElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'computed_label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_for_comparison');

  cache_nls.addPropertyIfDefined(properties, this, 'unused_label');
  cache_nls.addPropertyIfDefined(properties, this, 'hidden_label');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.LabelElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.LabelElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};
/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.LabelElement
 *
 * @desc Returns a text string representation of the label element 
 *
 * @return {String} Returns string represention the LabelElement object
 */
 
OpenAjax.a11y.cache.LabelElement.prototype.toString = function () {
 if (this.computed_label.length) 
   return "label: " + this.computed_label; 
 else
   return "label: empty"; 
};

/* ---------------------------------------------------------------- */
/*                       InputElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor InputElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a InputElement object used to hold information about input elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the input element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the input element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the control element in the document in relationship to other control elements
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {String}      type                  - Type of input element  
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 * @property  {String}      name_attribute        - Text content of the name attribute  
 *
 * @property  {String}      computed_label                 - Calculated label for the input element 
 * @property  {Number}      computed_label_length          - Length of the label property 
 * @property  {Number}      computed_label_source          - Constant representing how a label was calculated 
 * @property  {String}      computed_label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 *
 * @property  {LabelElement}     label_element    - Reference to any label element that this input is nested in
 * @property  {FieldsetElement}  fieldset_element - Reference to any fieldset elements this input is nested in
 *
 * @property  {String}      readonly   - The value of the readonly attribute 
 * @property  {String}      disabled   - The value of the disabled attribute
 * @property  {String}      value      - The value of the readonly attribute 
 * @property  {String}      checked    - The value of the disabled attribute
 *
 * @property  {Boolean}  is_owned       - True if this control is owned by another widget 
 * @property  {Array}    owner_controls - Array of all the widgets that own this widget (NOTE: More than one owner is an error)    
 */

OpenAjax.a11y.cache.InputElement = function (dom_element, control_info) {

  var node = dom_element.node;

  dom_element.is_interactive = true;

  this.dom_element = dom_element;
  this.cache_id    = "";
  this.document_order = 0;
  
  this.value   = node.value; 
  this.checked = node.checked;

  this.name_attribute = node.getAttribute('name');
  this.required       = node.getAttribute('required');
  this.aria_required  = node.getAttribute('aria-required');
  this.aria_invalid   = node.getAttribute('aria-invalid');

  this.control_type  = OpenAjax.a11y.CONTROL_TYPE.UNKNOWN; 

  this.type = "text";  
  if (node.type) this.type = node.type; 

  switch (node.type) {
 
  case 'button':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.BUTTON_INPUT; 
    break;

  case 'file':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.FILE; 
    break;
    
  case 'checkbox':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.CHECKBOX; 
    break;
    
  case 'radio':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.RADIO; 
    break;
    
  case 'text':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.TEXT; 
    break;
    
  case 'password':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.PASSWORD; 
    break;
    
  case 'hidden':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.HIDDEN; 
    break;
    
  case 'image':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.IMAGE; 
    break;

  case 'submit':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.SUBMIT; 
    break;
    
  case 'reset':
    this.control_type  = OpenAjax.a11y.CONTROL_TYPE.RESET; 
    break;
  
  default:
    break; 
  }
 
  this.readonly  = node.readonly;
  this.disabled  = node.disabled;
 
  this.label_element  = control_info.label_element;
  this.fieldset_element = control_info.fieldset_element;

  this.is_owned = false;
  this.owner_controls = [];
  
};

/**
 * @method addOwnerControl
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Adds a ARIA owner control element reference
 *
 * @param  {WidegtElement} owner_control   - Cache control element object to add 
 */

OpenAjax.a11y.cache.InputElement.prototype.addOwnerControl = function (owner_control) {

  if (owner_control) {
   this.is_owned = true;
   this.owner_controls.push(owner_control);
  }  
}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.InputElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.InputElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.InputElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  cache_nls.addPropertyIfDefined(attributes, this, 'name');
  cache_nls.addPropertyIfDefined(attributes, this, 'maxlength');
  cache_nls.addPropertyIfDefined(attributes, this, 'readonly');
  cache_nls.addPropertyIfDefined(attributes, this, 'value');
  cache_nls.addPropertyIfDefined(attributes, this, 'required');
  cache_nls.addPropertyIfDefined(attributes, this, 'aria_required');
  cache_nls.addPropertyIfDefined(attributes, this, 'aria_invalid');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.InputElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'label_for_comparison');
  cache_nls.addPropertyIfDefined(properties, this, 'is_widget');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.InputElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.InputElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method getLabelNLS
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an object with an NLS localized string and style properties
 *       If label is empty a missing label message will the returned 
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.InputElement.prototype.getLabelNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var label_style = {};
  
  if (this.computed_label_length) {
    return this.computed_label;
  }
  else {
    return cache_nls.getNLSMissingLabelMessage();
  }
  
};

/**
 * @method getLabelSourceNLS
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns an object with an NLS localized information on the source of the label
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.InputElement.prototype.getLabelSourceNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  return cache_nls.getValueNLS('computed_label_source', this.computed_label_source);
  
};



/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.InputElement
 *
 * @desc Returns a text string representation of the input element 
 *
 * @return {String} Returns string represention the InputElement object
 */
 
OpenAjax.a11y.cache.InputElement.prototype.toString = function () {
  
  return "input: " + this.type;
  
};

/* ---------------------------------------------------------------- */
/*                       ButtonElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor ButtonElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a ButtonElement object used to hold information about button elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the button element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element  - Reference to the dom element representing the button element
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {String}      name_attribute  - Value of the name attribute
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 *
 * @property  {FieldsetElement}  fieldset_element - Reference to any fieldset elements this button element is nested in
 *
 * @property  {String}     computed_label                  - Calculated label for the button element 
 * @property  {Number}     computed_label_length           - Length of the label property 
 * @property  {String}     computed_ label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 *
 * @property  {String}     readonly              - The value of the readonly attribute 
 * @property  {String}     disabled              - The value of the disabled attribute
 *
 * @property  {Boolean}  is_owned       - True if this control is owned by another widget 
 * @property  {Array}    owner_controls - Array of all the widgets that own this widget (NOTE: More than one owner is an error)    
 */

OpenAjax.a11y.cache.ButtonElement = function (dom_element, control_info) {

  dom_element.is_interactive = true;

  this.dom_element = dom_element;
  this.cache_id    = "";
  
  this.child_cache_elements = [];
 
  var node = dom_element.node;
 
  this.control_type   = OpenAjax.a11y.CONTROL_TYPE.BUTTON_ELEMENT; 
 
  this.name_attribute = node.getAttribute('name');
  
  this.readonly  = node.readonly;
  this.disabled  = node.disabled;
 
  this.fieldset_element = control_info.fieldset_element;

  this.is_owned = false;
  this.owner_controls = [];
  
};

/**
 * @method addOwnerControl
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Adds a ARIA owner control element reference
 *
 * @param  {WidegtElement} owner_control   - Cache control element object to add 
 */

OpenAjax.a11y.cache.ButtonElement.prototype.addOwnerControl = function (owner_control) {

  if (owner_control) {
   this.is_owned = true;
   this.owner_controls.push(owner_control);
  }  
}; 


/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.ButtonElement.prototype.addChildControl = function (child_control) {
  if (child_control) {
    this.child_cache_elements.push(child_control); 
  }  
}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'label_for_comparison');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method getLabelNLS
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an object with an NLS localized string and style properties
 *       If label is empty a missing label message will the returned 
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getLabelNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var label_style = {};
  
  if (this.computed_label_length) {
    return this.computed_label;
  }
  else {
    return cache_nls.getNLSMissingLabelMessage();
  }
  
};

/**
 * @method getLabelSourceNLS
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns an object with an NLS localized information on the source of the label
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.ButtonElement.prototype.getLabelSourceNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var label_style = {};
  
  return cache_nls.getValueNLS('computed_label_source', this.computed_label_source);
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.ButtonElement
 *
 * @desc Returns a text string representation of the button element 
 *
 * @return {String} Returns string represention the ButtonElement object
 */
 
OpenAjax.a11y.cache.ButtonElement.prototype.toString = function () {
 return "button"; 
};

/* ---------------------------------------------------------------- */
/*                    TextareaElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor TextareaElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a TextareaElement object used to hold information about textarea elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the textarea element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the textarea element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the control element in the document in relationship to other control elements
 *
 * @property  {String}      name_attribute  - Value of the name attribute
 * @property  {String}      type            - Type of form control
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 *
 * @property  {String}      computed_label                 - Calculated label for the textarea element 
 * @property  {Number}      computed_label_length          - Length of the label property 
 * @property  {String}      computed_label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 *
 * @property  {LabelElement}     label_element    - Reference to any label element that this input is nested in
 * @property  {FieldsetElement}  fieldset_element - Reference to any fieldset elements this input is nested in
 *
 * @property  {String}      rows       - The value of the rows attribute 
 * @property  {String}      cols       - The value of the cols attribute
 *
 * @property  {String}      readonly   - The value of the readonly attribute 
 * @property  {String}      disabled   - The value of the disabled attribute
 *
 * @property  {Boolean}  is_owned       - True if this control is owned by another widget 
 * @property  {Array}    owner_controls - Array of all the widgets that own this widget (NOTE: More than one owner is an error)    
 */

OpenAjax.a11y.cache.TextareaElement = function (dom_element, control_info) {

  var node = dom_element.node;

  dom_element.is_interactive = true;

  this.dom_element    = dom_element;
  this.cache_id       = "";
  this.document_order = 0;
  this.type = "textarea";  

  this.control_type = OpenAjax.a11y.CONTROL_TYPE.TEXTAREA;
 
  this.label_element  = control_info.label_element;
  this.fieldset_element = control_info.fieldset_element;
  
  this.name_attribute  = node.getAttribute('name');
  
  this.rows = node.rows; 
  this.cols = node.cols; 
 
  this.readonly  = node.readonly;
  this.disabled  = node.disabled;

  this.is_owned = false;
  this.owner_controls = [];
  
};

/**
 * @method addOwnerControl
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Adds a ARIA owner control element reference
 *
 * @param  {WidegtElement} owner_control   - Cache control element object to add 
 */

OpenAjax.a11y.cache.TextareaElement.prototype.addOwnerControl = function (owner_control) {

  if (owner_control) {
   this.is_owned = true;
   this.owner_controls.push(owner_control);
  }  
}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'label_for_comparison');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method getLabelNLS
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an object with an NLS localized string and style properties
 *       If label is empty a missing label message will the returned 
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getLabelNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  if (this.computed_label_length) {
    return this.computed_label;
  }
  else {
    return cache_nls.getNLSMissingLabelMessage();
  }
  
};


/**
 * @method getLabelSourceNLS
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns an object with an NLS localized information on the source of the label
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.TextareaElement.prototype.getLabelSourceNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  return cache_nls.getValueNLS('computed_label_source', this.computed_label_source);
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.TextareaElement
 *
 * @desc Returns a text string representation of the textarea element 
 *
 * @return {String} Returns string represention the Element object
 */
 
OpenAjax.a11y.cache.TextareaElement.prototype.toString = function () {
 return "Textarea"; 
};

/* ---------------------------------------------------------------- */
/*                      SelectElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor SelectElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a SelectElement object used to hold information about select elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the select element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the select element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the control element in the document in relationship to other control elements
 *
 * @property  {String}      name_attribute  - Value of the name attribute
 * @property  {String}      type            - String indicating the type of form control
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Array}       option_elements       - Array of child cache option elements  
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 *
 * @property  {LabelElement}     label_element    - Reference to any label element that this input is nested in
 * @property  {FieldsetElement}  fieldset_element - Reference to any fieldset elements this select element is nested in
 *
 * @property  {String}      computed_label                 - Calculated label for the select element 
 * @property  {Number}      computed_label_length          - Length of the label property 
 * @property  {String}      computed_label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 * @property  {String}      size                  - The value of the size attribute 
 * @property  {String}      multiple              - The value of the multiple attribute
 *
 * @property  {Boolean}  is_owned       - True if this control is owned by another widget 
 * @property  {Array}    owner_controls - Array of all the widgets that own this widget (NOTE: More than one owner is an error)    
 */

OpenAjax.a11y.cache.SelectElement = function (dom_element, control_info) {

  dom_element.is_interactive = true;

  this.dom_element    = dom_element;
  
  this.cache_id       = "";
  this.document_order = 0;
  
  this.child_cache_elements = [];
  
  this.option_elements = [];
 
  this.control_type = OpenAjax.a11y.CONTROL_TYPE.SELECT;

  var node = dom_element.node;

  this.name_attribute  = node.getAttribute('name');
  this.type = "select";
  
  this.size   = node.size;
  this.multiple = node.multiple;
 
  this.label_element  = control_info.label_element;
  this.fieldset_element = control_info.fieldset_element;
  
  this.is_owned = false;
  this.owner_controls = [];
   
};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.SelectElement.prototype.addChildControl = function (child_control) {

 if (child_control) {
  this.child_cache_elements.push(child_control); 
 }  

};

/**
 * addOption
 * 
 * @desc add a OptionElement object reference to the tree of   
 *
 * @param  child_control    Object control cache element object  
 *
 * @return  nothing
 */

OpenAjax.a11y.cache.SelectElement.prototype.addOption = function (option_element) {

  if (option_element) {
    this.option_elements.push(option_element); 
    option_element.document_order = this.option_elements.length;
    option_element.cache_id    = this.cache_id + "_" + this.option_elements.length;
  }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.SelectElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.SelectElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.SelectElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.SelectElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'label_for_comparison');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.SelectElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.SelectElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method getLabelNLS
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an object with an NLS localized string and style properties
 *       If label is empty a missing label message will the returned 
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.SelectElement.prototype.getLabelNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var label_style = {};
  
  if (this.computed_label_length) {
    return this.computed_label;
  }
  else {
    return cache_nls.getNLSMissingLabelMessage();
  }
  
};


/**
 * @method getLabelSourceNLS
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns an object with an NLS localized information on the source of the label
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.SelectElement.prototype.getLabelSourceNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var label_style = {};
  
  return cache_nls.getValueNLS('computed_label_source', this.computed_label_source);
  
};

/**
 * @method addOwnerControl
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Adds a ARIA owner control element reference
 *
 * @param  {WidegtElement} owner_control   - Cache control element object to add 
 */

OpenAjax.a11y.cache.SelectElement.prototype.addOwnerControl = function (owner_control) {

  if (owner_control) {
   this.is_owned = true;
   this.owner_controls.push(owner_control);
  }  
}; 

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.SelectElement
 *
 * @desc Returns a text string representation of the select element 
 *
 * @return {String} Returns string represention the SelectElement object
 */
 
OpenAjax.a11y.cache.SelectElement.prototype.toString = function () {
  return "select: " + this.option_elements.length + " options"; 
};

/* ---------------------------------------------------------------- */
/*                       OptgroupElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor OptgroupElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a OptgroupElement object used to hold information about optgroup elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the optgroup element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element  - Reference to the dom element representing the optgroup element
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 *
 * @property  {SelectElement}  select_element     - Reference to the select element that this optgroup is nested in
 *
 * @property  {String}      computed_label                 - Calculated label for the select element 
 * @property  {Number}      computed_label_length          - Length of the label property 
 * @property  {String}      computed_label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 */

OpenAjax.a11y.cache.OptgroupElement = function (dom_element, control_info) {

 this.dom_element = dom_element;
 this.cache_id    = "";
 
 this.child_cache_elements = [];
         
 this.control_type = OpenAjax.a11y.CONTROL_TYPE.OPTGROUP;
 
 this.select_element = control_info.select_element;
         
 this.computed_label = dom_element.node.computed_label;
 if (this.computed_label) { 
   this.computed_label_length = this.computed_label.length;
   this.computed_label_for_comparison = this.computed_label.normalizeSpace().toLowerCase();
 }  
 else {
   this.computed_label_length = 0;
   this.computed_label_for_comparison = "";
 }  
 
};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidgetElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.addChildControl = function (child_control) {

 if (child_control) {
  this.child_cache_elements.push(child_control); 
 }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'label_for_comparison');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.OptgroupElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};
/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.OptgroupElement
 *
 * @desc Returns a text string representation of the optgroup element 
 *
 * @return {String} Returns string represention the OptgroupElement object
 */
 
OpenAjax.a11y.cache.OptgroupElement.prototype.toString = function () {
 return "OPTGROUP with " + this.child_cache_elements.length + " options"; 
};

/* ---------------------------------------------------------------- */
/*                      OptionElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor OptionElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a OptgroupElement object used to hold information about optgroup elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the optgroup element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element        - Reference to the dom element representing the optgroup element
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {Number}      control_type       - Constant indicating the type of cache control object  
 *
 * @property  {SelectElement}  select_element  - Reference to the select element that this optgroup is nested in
 *
 * @property  {String}         value           - Value of the value attribute 
 */

OpenAjax.a11y.cache.OptionElement = function (dom_element, control_info) {

 this.dom_element = dom_element;
 this.cache_id    = "";
 
 this.control_type   = OpenAjax.a11y.CONTROL_TYPE.OPTION;
 
 this.select_element = control_info.select_element;
         
 this.value = dom_element.node.value;
 
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.OptionElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.OptionElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.OptionElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.OptionElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'label_for_comparison');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.OptionElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.OptionElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.OptionElement
 *
 * @desc Returns a text string representation of the option element 
 *
 * @return {String} Returns string represention the OptionElement object
 */
 
OpenAjax.a11y.cache.OptionElement.prototype.toString = function () {
 return "OPTION with value=" + this.value; 
};

/* ---------------------------------------------------------------- */
/*                       WidgetElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor WidgetElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a InputElement object used to hold information about input elements
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the input element 
 * @param  {ControlInfo}  control_info  - Information about the parent controls
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the input element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the control element in the document in relationship to other control elements
 *
 * @property  {Boolean}     has_owns              - if the widget has aria-owns, use this to calculate children 
 * @property  {Array}       child_cache_elements  - Array of child cache control elements as part of cache control tree 
 * @property  {String}      type                  - String indicating the type of input element  
 * @property  {Number}      control_type          - Constant indicating the type of cache control object  
 * @property  {String}      name_attribute        - Text content of the name attribute  
 *
 * @property  {String}  computed_label                 - Calculated label for the input element 
 * @property  {Number}  computed_label_length          - Length of the label property 
 * @property  {Number}  computed_label_source          - Constant representing how a label was calculated 
 * @property  {String}  computed_label_for_comparison  - Label for comparison (lowercase, space normalization and trimmed)
 *
 * @property  {LabelElement}     label_element    - Reference to any label element that this input is nested in
 * @property  {FieldsetElement}  fieldset_element - Reference to any fieldset elements this input is nested in
 *
 * @property  {String}  readonly  - The value of the readonly attribute 
 * @property  {String}  disabled  - The value of the disabled attribute
 * @property  {String}  value     - The value of the readonly attribute 
 * @property  {String}  checked   - The value of the disabled attribute
 *
 * @property  {Boolean}  is_owned       - True if this widget is owned by another widget 
 * @property  {Array}    owner_controls - Array of all the widgets that own this widget (NOTE: More than one owner is an error)    
 */

OpenAjax.a11y.cache.WidgetElement = function (dom_element, control_info) {

  var node = dom_element.node;
 
  this.dom_element    = dom_element;
  this.has_owns       = dom_element.hasOwns();
  this.cache_id       = "";
  this.document_order = 0;
  this.parent_widget  = control_info.parent_widget;
  
  this.child_cache_elements = [];
  this.type    = node.type; 
  this.value   = node.value; 
  this.checked = node.checked;

  this.name_attribute = node.getAttribute('name');
  this.required       = node.getAttribute('required');
  this.aria_required  = node.getAttribute('aria-required');
  this.aria_invalid   = node.getAttribute('aria-invalid');

  this.control_type   = OpenAjax.a11y.CONTROL_TYPE.WIDGET; 
  
  this.label_element    = control_info.label_element;
  this.fieldset_element = control_info.fieldset_element;

  this.aria_attributes_with_invalid_values  = [];
  this.aria_attributes_missing              = []; 
  
  this.is_owned = false;
  this.owner_controls = [];
  
};

/**
 * @method addChildControl
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Adds a cache control element to the tree representation of control elements
 *
 * @param  {WidegtElement | ButtonElement | FieldsetElement | FormElement | InputElement | LabelElement| LegendElement | OptgroupElement | OptionElement | SelectElement | TextareaElement } control_element   - Cache control element object to add 
 * @param  {Boolean} override_owns  - If true, allow child elements to be added if the widget has an owns property
 */

OpenAjax.a11y.cache.WidgetElement.prototype.addChildControl = function (child_control, override_owns) {

  if (this.has_owns && ((typeof override_owns != 'boolean') || ((typeof override_owns === 'boolean') && !override_owns))) return;

  if (child_control) {
   this.child_cache_elements.push(child_control);
  }  
}; 

/**
 * @method addOwnerControl
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Adds a ARIA owner control element reference
 *
 * @param  {WidegtElement} owner_control   - Cache control element object to add 
 */

OpenAjax.a11y.cache.WidgetElement.prototype.addOwnerControl = function (owner_control) {

  if (owner_control) {
   this.is_owned = true;
   this.parent_widget = owner_control;
   this.owner_controls.push(owner_control);
  }  
}; 

/**
 * @method getOwnedIds
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 * 
 * @desc Returns an array of strings representing the ids in the aria-owns property
 *
 * @return {Array} Returns an array of string objects represrenting the ids of the aria-owns property
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getOwnedIds = function () {

  var aria_owns = this.dom_element.aria_owns;
  var return_array = [];

  if (typeof aria_owns === 'string' && (aria_owns.length > 0)) {
  
    if (aria_owns.indexOf(' ') > 0) {
      return_array = aria_owns.split(' ');
    }
    else {
      return_array.push(aria_owns);
    }
    
  }
  
  return return_array;

}; 

/**
 * @method hasChildRole
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Tests if a widget has a child ARIA element with a certain role
 *
 * @param {String}  role -  Role to find 
 *
 * @return {Boolean} Returns true if widget has child element with role, otherwise false
 */

OpenAjax.a11y.cache.WidgetElement.prototype.hasChildRole = function (role) {

   function checkCacheChildren(list) {
   
     var flag = false;
   
     for (var i = 0; (i < list.length); i++) {
     
       var item = list[i];
     
       if (item.dom_element.role === role) {
         flag = true;
         break;
       }
       else {
         if (item.child_cache_elements && item.child_cache_elements.length) {
           flag = checkCacheChildren(item.child_cache_elements);
         }
       }  
     }
   
     return flag;
     
   }

   return checkCacheChildren(this.child_cache_elements);
  
};

/**
 * @method hasParentRole
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Tests if a widget has a parent element with a certain role
 *
 * @param {String}  role -  Role to find 
 *
 * @return {Boolean} Returns true if widget has child element with role, otherwise false
 */

OpenAjax.a11y.cache.WidgetElement.prototype.hasParentRole = function (role) {

   function checkParentRole(widget) {
   
     if (!widget) return false;
     
     if (widget.dom_element.role === role) return true;
     
     return checkParentRole(widget.parent_widget);
   
   }
   
   return checkParentRole(this.parent_widget);

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  cache_nls.addPropertyIfDefined(attributes, this, 'name');
  cache_nls.addPropertyIfDefined(attributes, this, 'maxlength');
  cache_nls.addPropertyIfDefined(attributes, this, 'readonly');
  cache_nls.addPropertyIfDefined(attributes, this, 'value');
  cache_nls.addPropertyIfDefined(attributes, this, 'required');
  cache_nls.addPropertyIfDefined(attributes, this, 'aria_required');
  cache_nls.addPropertyIfDefined(attributes, this, 'aria_invalid');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'computed_label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_for_comparison');
  cache_nls.addPropertyIfDefined(properties, this, 'is_widget');
  cache_nls.addPropertyIfDefined(properties, this, 'is_section');
  cache_nls.addPropertyIfDefined(properties, this, 'is_owned');
  cache_nls.addPropertyIfDefined(properties, this, 'owner_controls');
  
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};


/**
 * @method getLabelNLS
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an object with an NLS localized string and style properties
 *       If label is empty a missing label message will the returned 
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getLabelNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var label_style = {};
  
  if (this.computed_label_length) {
    return this.computed_label;
  }
  else {
    return cache_nls.getNLSMissingLabelMessage();
  }
  
};

/**
 * @method getLabelSourceNLS
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns an object with an NLS localized information on the source of the label
 *
 * @return {String | Object} Returns a String if the label has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.WidgetElement.prototype.getLabelSourceNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  return cache_nls.getValueNLS('computed_label_source', this.computed_label_source);
  
};



/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.WidgetElement
 *
 * @desc Returns a text string representation of the input element 
 *
 * @return {String} Returns string represention the InputElement object
 */
 
OpenAjax.a11y.cache.WidgetElement.prototype.toString = function () {
  
  return this.dom_element.tag_name + ": " + this.dom_element.role;
  
};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                       DOMElementCache                            */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor DOMElementCache
 *
 * @memberOf OpenAjax.a11y.cache
 * 
 * @desc Creates a DOMElementCache object for represeting a DOM in a web browser
 *         
 * @property {Array}  dom_elements        - A simple array of all the DOMElement objects in the cache
 * @property {Array}  dom_text            - A simple array of all the DOMText objects in the cache
 * @property {Object} page_element        - The dom element that is used as a place to collect results for rules with scope of page
 * @property {Array}  child_dom_elements  - The roor of a tree of DOMElement objects representing the node relationships on the DOM
 * @property {String} sort_property       - String  The DOMElement property the dom_elements array is sorted by
 * @property {Number} length              - The running length of the dom_elements array used for calculating the cache_id property of a DOMElement
 */

OpenAjax.a11y.cache.DOMElementCache = function () {

 this.dom_elements = [];
 this.dom_text = [];
 
 this.child_dom_elements = [];
 
 this.page_element = null;
 
 this.sort_property = 'document_order';
 this.length = 0;
 this.text_length = 0;

};

/** 
 * @method initCache
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Initializes properties of the DOMElementCache
 *
 * @return Nothing
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.initCache = function () {

 this.dom_elements         = [];
 this.child_dom_elements   = [];
 this.sort_property        = 'document_order';
 this.page_element         = null;
 this.length               = 0;
 
};

/** 
 * @method getPageElement
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Gets the DOM node used to contain page level rule results
 *
 * @return {DOMElement} DOM element object used to contain page level rule results
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.getPageElement = function () {

 return this.page_element;
 
};

/**
 * @method addDOMElement
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Adds a DOMElement object to the array of all DOMElements and calculates the elements cache ID
 *
 * @param {DOMElement Object}  dom_element  - DOMElement object to add 
 *
 * @return  {Number}  Returns the current number of elements in the array of DOMElements
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.addDOMElement = function (dom_element) {

  // item must exist and have the position property
  if (dom_element) {   
    this.length = this.length + 1;
    dom_element.document_order = this.length;
    dom_element.cache_id = "element_" + this.length;
    this.dom_elements.push( dom_element );
    
    // only one page element per page
    if ((this.page_element === null) &&
        (dom_element.tag_name === 'body')) {
      this.page_element = dom_element;
    } 
 }

 return this.dom_elements.length;

};

/**
 * @method addDOMText
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Adds a DOM text object to the array of all DOM text and calculates the cache ID
 *
 * @param {DOMText Object}  dom_text  - DOM text object to add 
 *
 * @return  {Number}  Returns the current number of elements in the array of DOM text objects
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.addDOMText = function (dom_text) {

 // item must exist and have the position property
 if (dom_text) {
  this.text_length  += 1;
  dom_text.document_order = this.text_length;
  dom_text.cache_id       = "text_" + this.text_length;
  this.dom_text.push(dom_text);
 }

 return this.dom_text.length;

};

/**
 * @method addChild
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Adds a DOMElement or DOMText object to the root level of the tree reflecting the DOM of document  
 *
 * @param {DOMElement or DOMText object} dom_object  - DOMElement or DOMText object to add to the tree  
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.addChild = function (dom_object) {

  if (dom_object) {
    this.child_dom_elements.push(dom_object);
  }
};

/**
 * @method getDOMElementById
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Returns the DOMElement object with the id attribute value
 *
 * @param {String} id - id of DOMElement object to find
 *
 * @return {DOMElement} Returns DOMElement with the associated id if found, otherwise null
 */
 
OpenAjax.a11y.cache.DOMElementCache.prototype.getDOMElementById = function (id) {

  var i;
  var dom_elements_len = this.dom_elements.length;

  if (id && id.length) {
    for (i=0; i < dom_elements_len; i++) {
      if (this.dom_elements[i].id == id) {
        return this.dom_elements[i];
      }
    } // end loop
  }
  return null;
};

/**
 * @deprecated getDOMElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Finds the the DOMElement object with the matching cache ID value
 *
 * @param {String} cache_id  - cache_id of DOMElement object to find
 *
 * @return {DOMElement} Returns DOMElement with the associated cache ID if found, otherwise null
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.getDOMElementByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Finds the the DOMElement object with the matching cache ID value
 *
 * @param {String} cache_id  - cache_id of DOMElement object to find
 *
 * @return {DOMElement} Returns DOMElement with the associated cache ID if found, otherwise null
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.getItemByCacheId = function (cache_id) {

  var i;
  var dom_elements_len = this.dom_elements.length;

  if (cache_id && cache_id.length) {
    for (i=0; i < dom_elements_len; i++) {
      if (this.dom_elements[i].cache_id == cache_id) {
        return this.dom_elements[i];
      }
    } // end loop
  }
  return null;
};


/**
 * @method getItemsByNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Returns an array of cache items with node results based on the filter 
 *
 * @param  {Number}  filter  - Filter for returning items with node results of a 
 *                             particular type(s)  
 *
 * @return {Array} Returns array of cache items, can be empty
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.getItemsByNodeResults = function (filter, all_flag) {

  return OpenAjax.a11y.util.getItemsByNodeResults(this.dom_elements, filter, all_flag);

};

/**
 * @method sortDOMElements
 *
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Sorts the dom_elements array based on a property of the DOMElement object
 *
 * @param  {String}  property  - DOMElement object property used to sort the array
 * @param  {Boolean} ascending - Boolean  true if sort in ascending order; false in descending order
 *
 * @return  {Boolean}  true if list was sorted, false if not
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.sortDOMElements = function(property, ascending ) {

  var swapped = false;
  var temp = null;
  var i;

  // if the property does not exist or if the cache is empty return false
  if( this.dom_elements &&
    this.dom_elements.length &&
    !this.dom_elements[0][property] ) {
    return false;
  }

  var dom_elements_len = this.dom_elements.length;

  if( ascending ) {
    do{
      swapped = false;
      for (i = 1; i < dom_elements_len; i++ ) {
        if (this.dom_elements[i-1][property] > this.dom_elements[i][property]) {
          // swap the values
          temp = this.dom_elements[i-1];
          this.dom_elements[i-1] = this.dom_elements[i];
          this.dom_elements[i] = temp;
          swapped = true;
        }
      } // end loop
    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < dom_elements_len; i++) {
        if (this.dom_elements[i-1][property] < this.dom_elements[i][property]) {
          // swap the values
          temp = this.dom_elements[i-1];
          this.dom_elements[i-1] = this.dom_elements[i];
          this.dom_elements[i] = temp;
          swapped = true;
        }
      } // end loop
    } while (swapped);
  }

  this.sort_property = property;

  return true;
};

/**
 * @method getTextFromIds
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc    Gets the accessible text content from a list of ids
 *
 * @note    Used in calculating accessible names, labels and descriptions
 *
 * @param   {String} ids - a space separated list of ids
 *
 * @return  {String} Returns a string with the concatenated text content of the elements with ids  
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.getTextFromIds = function( ids ) {

  var i;
  var text_array = [];
  var id_array = ids.split(' ');
  var id_array_len = id_array.length;
  var dom_element = null;

  for (i=0; i<id_array_len; i++) {
    dom_element = this.getDOMElementById(id_array[i]);
    if (dom_element) {
      text_array.push(dom_element.getText());
    }
  } // end loop

  return text_array.join(' ');
};

/**
 * @method checkForUniqueIDs
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementCache
 *
 * @desc Check DOMElements for unique ids and set id_unique property for all DOMElements in the cache
 *       Sets the 'id_unique' property on DOMElement objects that do not have unique ID attribute values
 */

OpenAjax.a11y.cache.DOMElementCache.prototype.checkForUniqueIDs = function () {

  var i;
  var j;
 
  var dom_elements = this.dom_elements;
  var dom_elements_len1 = dom_elements.length-1;
  var dom_elements_len2 = dom_elements.length;
 
  var de1;
  var de2;

  for (i = 0; i < dom_elements_len1; i++ ) {
    de1 = dom_elements[i];

    for (j=i+1; j < dom_elements_len2; j++) {
      de2 = dom_elements[j];

      if(de1.id.length && de2.id.length && de1.id == de2.id) {
        de1.id_unique = OpenAjax.a11y.ID.NOT_UNIQUE;
        de2.id_unique = OpenAjax.a11y.ID.NOT_UNIQUE;
      }      
    }  
  }
};


/* ---------------------------------------------------------------- */
/*                       DOMText Object                             */ 
/* ---------------------------------------------------------------- */

/** 
 * @constructor DOMText
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc DOMText object represents DOM nodes of type text
 * 
 *
 * @param  {Object}      node           - The DOM text node 
 * @param  {DOMElement}  parent_element - DOMElement object that is the current parent in the tree
 *
 * @property  {DOMElement}  parent_landmark      - LandmarkElement object that contains this element
 *
 * @property  {LandmarkElement}  parent_landmark - LandmarkElement object that contains the text content
 *
 * @property {Number}  type - Type of DOM node element or text
 * @property {String}  text - Text content of DOM text node
 *
 * @property {String}   text_normalized         - Normalized text in the node
 * @property {Number}   text_length             - length of the normalized text in the node 
 * 
 * @property {String}  cache_id        - String that uniquely identifies the cache element in the DOMCache
 * @property {Number}  document_order  - The ordinal position of this DOM text node in the DOM
 *
 * @property {Object}  computed_style  - Object that contains information about run time styling of the node
 * @property {Object}  events          - Object that contains information about event handlers attached to the node and its descendents
 *
 * @property {Boolean}  has_rule_results       - Boolean indicating if the node has any rule results
 * @property {Array}    rules_violations       - Array of NodeResult objects with severity of 'Violation'
 * @property {Array}    rules_manual_checks    - Array of NodeResult objects with severity of 'Manual Check'
 * @property {Array}    rules_warnings         - Array of NodeResult objects with severity of 'Warning'
 * @property {Array}    rules_passed           - Array of NodeResult objects with severity of 'Passed'
 * @property {Array}    rules_hidden           - Array of NodeResult objects with severity of 'Hidden'
 */
 
OpenAjax.a11y.cache.DOMText = function (node, parent_element) {

  this.type = Node.TEXT_NODE;
  this.text = node.data;
  this.parent_element = parent_element;
 
  this.parent_landmark      = null;
 
  this.text_normalized = this.text.normalizeSpace();
  var text_length      = this.text_normalized.length;
  this.text_length     = text_length; 
  
  parent_element.addToCharacterCount(text_length);
 
  this.computed_style = parent_element.computed_style;
 
  // Create areas to store rule results associates with this node
  this.has_rule_results = false;
  this.rules_violations                = [];
  this.rules_manual_checks             = [];
  this.rules_warnings                  = [];
  this.rules_passed                    = [];
  this.rules_page                      = [];
  this.rules_hidden                    = [];
};

/**
 * @method addText
 *
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc   Check DOMElement for presence of attribute with specified value
 *
 * @param  {String} text  - text content to add
 *
 * @return {Number}  Length of the normailized text content of the DOM text node
 */

OpenAjax.a11y.cache.DOMText.prototype.addText = function (text) {

  this.text += text;
  
  this.text_normalized = this.text.normalizeSpace();
  
  var text_length = this.text_normalized.length;

  this.parent_element.addToCharacterCount(text_length - this.text_length);

  this.text_length = text_length;
  
};


/**
 * @method getId
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc   If defined, return the id of the dom node containing this text
 *
 * @return {String} If defined return id value, else empty string
 */

OpenAjax.a11y.cache.DOMText.prototype.getId = function () {

  return this.parent_element.getId();

};

/**
 * @method getClassName
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc   If defined, return the class attribute value of the dom node containing this text
 *
 * @return {String} If defined return class value value, else empty string
 */

OpenAjax.a11y.cache.DOMText.prototype.getClassName = function () {

  return this.parent_element.getClassName();

};

/**
 * @method getParentLandmark
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc   If defined, return the parent landmark element information
 *
 * @return {String} If defined return class value value, else empty string
 */

OpenAjax.a11y.cache.DOMText.prototype.getParentLandmark = function () {

  return this.parent_element.getParentLandmark();

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.DOMText.prototype.getNodeResults = function () {
 
  function addResultNodes(items) {
  
    var len = items.length;
    
    for (var i = 0; i < len; i++ ) {
      result_nodes.push(items[i]);
    }
    
  }

  var result_nodes = [];
  
  addResultNodes(this.rules_violations);
  addResultNodes(this.rules_manual_checks);
  addResultNodes(this.rules_warnings);
  addResultNodes(this.rules_passed);
  addResultNodes(this.rules_page); 
  addResultNodes(this.rules_hidden); 
  
  return result_nodes;
  
};

/**
 * @method getAccessibility
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns the worst severity level of rule results  
 *
 * @return {Object} Results an object wiith two properties: 'severity' : nls value of the severity, 'style' : a severity styling constant
 */

OpenAjax.a11y.cache.DOMText.prototype.getAccessibility = function () {
   
  var cache_nls      = OpenAjax.a11y.cache_nls;
  var RESULT_VALUE       = OpenAjax.a11y.RESULT_VALUE;

  var severity = cache_nls.getResultValueNLS(RESULT_VALUE.NONE); 

  if (this.rules_hidden.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.HIDDEN);
  }

  if (this.rules_page.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.PAGE);
  }
  
  if (this.rules_passed.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.PASS);
  }

  if (this.rules_manual_checks.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.MANUAL_CHECK);
  }

  if (this.rules_warnings.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.WARNING);
  }
  
  if (this.rules_violations.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.VIOLATION);
  }
  
  return severity;
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns an empty array, text nodes do not have attributes
 *
 * @return {Array} Returns a empty array
 */

OpenAjax.a11y.cache.DOMText.prototype.getAttributes = function (unsorted) {

  return [];

};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns an empty array, text nodes do not have events
 *
 * @return {Array} Returns a empty array
 */

OpenAjax.a11y.cache.DOMText.prototype.getEvents = function (unsorted) {

  return [];

};


/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns an array of styling information for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of NLS objects for styling
 */

OpenAjax.a11y.cache.DOMText.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
 
  var properties  = [];

  cache_nls.addPropertyIfDefined(properties, this, 'text_normalized');
  cache_nls.addPropertyIfDefined(properties, this, 'text_length');
  cache_nls.addPropertyIfDefined(properties, this, 'has_rule_results');
  
  cache_nls.addPropertyIfDefined(properties, this, 'parent_landmark');
  
  return properties;

};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns an array of styling information for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of NLS objects for styling
 */

OpenAjax.a11y.cache.DOMText.prototype.getStyle = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
 
  var properties  = [];

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'is_visible_onscreen');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'is_visible_to_at');

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'display');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'visibility');
  
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'color');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'opacity');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_color');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_image');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_repeat');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_position');

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'font_family');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'font_size');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'font_weight');

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'position');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'left');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'top');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'width');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'height');
  
  return properties;

};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number | Object} Returns the value of the property
 */

OpenAjax.a11y.cache.DOMText.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    if (typeof this.computed_style[property] == 'undefined') {
      if (typeof this.parent_element[property] == 'undefined') {
        if (this.parent_landmark) {
          if (typeof this.parent_landmark[property] == 'undefined') {
            if (typeof this.parent_landmark.dom_element[property] == 'undefined') {
              return null;
            }
            else {
              return this.parent_landmark.dom_element[property];
            }
          }
          else {  
            return this.parent_landmark[property];
          }  
        }  
      }  
      else {  
        return this.parent_element[property];
      }  
    }
    else {
      return this.computed_style[property];
    }  
  }
    
  return this[property];
};


/**
 * @method getColorContrastNodeResult
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns a node result for a color contrast rule
 *
 * @return {Object} Returns node result object of a color contrast rule
 */
 
OpenAjax.a11y.cache.DOMText.prototype.getColorContrastNodeResult = function() {

  function findColorContrastRule(node_results) {
   
    var node_results_len = node_results.length;
    
    for (var i = 0; i < node_results_len; i++ ) {
    
      var node_result = node_results[i];
      
      var rule_id = node_result.getRuleId();
      
      if (rule_id === "COLOR_1") return node_result;      
      if (rule_id === "COLOR_2") return node_result;
    
    }
  
    return null;
  }

  var nr = findColorContrastRule(this.rules_violations);
  if (nr) return nr;

  nr = findColorContrastRule(this.rules_manual_checks);
  if (nr) return nr;
  
  nr = findColorContrastRule(this.rules_warnings);
  if (nr) return nr;
  
  nr = findColorContrastRule(this.rules_passed);
  if (nr) return nr;
  
  nr = findColorContrastRule(this.rules_page);
  if (nr) return nr;
  
  nr = findColorContrastRule(this.rules_hidden);
  if (nr) return nr;
  
  return null;

};

/**
 * @method getText
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns text content of a DOMText element
 *
 * @return {String} Returns the text content dom text node
 */
 
OpenAjax.a11y.cache.DOMText.prototype.getText = function() {
  return this.text_normalized;
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.DOMText
 *
 * @desc Returns text representation of a DOMText element
 *
 * @return {String} Returns a string representing the DOM text node
 */
 
OpenAjax.a11y.cache.DOMText.prototype.toString = function(option) {
  var str;
  
  if (option == 'text') str = "'" +  this.text_normalized + "'";
  else str = this.parent_element.tag_name + ": '" +  this.text_normalized + "'";
  
  return str;
};


/* ---------------------------------------------------------------- */
/*                       DOMElement Object                          */ 
/* ---------------------------------------------------------------- */

/** 
 * @constructor DOMElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc The DOMElement object represents a dom node of a document
 *
 * @param {Object} node                     - DOM node object of the element
 * @param {Object} parent_element           - DOMElement object that is the parent element of this node
 * 
 * @property  {String}    cache_id            - String that uniquely identifies the cache element in the DOMCache
 * @property  {String}    xpath               - String that identifies the position of the element in the document
 *
 * @property {Array}      child_dom_elements  - The child DOMElement and DOMText objects of this DOMElement in the tree
 * @property {DOMElement} parent_element      - The parent DOMElement of this DOMElement in the tree
 *
 * @property {LandmarkElement}  parent_landmark - LandmarkElement object that contains this element
 *
 * @property {Number}     type                - Type of DOM node is element  
 * @property {Number}     document_order      - The ordinal position of this DOM element node in the DOM
 * 
 * @property {Object}     node                - Reference to the 'live' DOM element represented by this object
 * @property {String}     tag_name            - Tag name of the HTML element in lowercase characters (i.e. p, div, h1, span ...)
 * @property {Array}      aria_attributes     - Array of ARIA properties and states defined for the node
 *
 * @property {String}     id                  - id attribute value of the DOM node (can be empty)
 * @property {Number}     id_unique           - Indicates if id is defined, unique or has a duplicate in the document
 *
 * @property {Number}     character_count     - Count of text charcters in the immediate child DOM text nodes
 * 
 * @property {String}     class_name          - The value of the class attribute of the DOM node
 * @property {String}     role                - The value of the role attribute of the DOM node
 *
 * @property {String}     alt                 - String   The value of the alt attribute of the DOM node
 * @property {Boolean}    has_alt_attribute   - true if the alt attribute is defined, otherwise false 
 *
 * @property {String}     title               - The value of the title            attribute of the DOM node
 * @property {String}     aria_describedby    - The value of the aria-describedby attribute of the DOM node
 * @property {String}     aria_hidden         - The value of the aria-hidden      attribute of the DOM node
 * @property {String}     aria_label          - The value of the aria-label       attribute of the DOM node
 * @property {String}     aria_labelledby     - The value of the aria-labelledby  attribute of the DOM node
 * 
 * @property {Boolean}     has_activedescendant     - True if the current element has defined aria-activedescendent attribute, otherwise false
 * @property {Boolean}     ancestor_has_activedescendant - True if a ancestor element has defined aria-activedescendent attribute, otherwise false
 *
 * @property {String}     calculated_aria_description  - If aria-describedby defined this is a string of the 
 *                                                       description content 
 *
 * @property {Boolean}    has_role            - True if element has a role value, otherwise false
 * @property {Boolean}    has_owns            - True if element has a aria-owns property, otherwise false
 * @property {Boolean}    has_aria-attributes - True if element has a aria attributes, otherwise false
 * @property {Boolean}    is_widget           - True if element is a ARIA widget, otherwise false
 * @property {Boolean}    is_landmark         - True if element is a ARIA landmark, otherwise false
 * @property {Boolean}    is_live             - True if element is a ARIA live region, otherwise false
 * @property {Boolean}    is_section          - True if element is a section role, otherwise false
 * @property {Array}      aria_attributes     - Array of ARIA property and state attributes (i.e. attributes 
 *                                              beginning with 'aria-')
 *
 * @property {Array}      invalid_aria_attributes             - Array of attributes that start with aria-, but are not defined by aria
 * @property {Array}      aria_attributes_with_invlaid_values - Array of attributes who have 
 *
 *
 * @property {Object}     role_info         - Object containing information about a widget
 *
 * @property {Object}     events              - Object that contains information about events associated with the node
 * @property {Object}     computed_style      - Object that contains information about run time styling of the node
 *
 * @property {Boolean}    has_rule_results       - Boolean indicating if the node has any rule results
 * @property {Array}      rules_passed           - Array of NodeResult objects with severity of 'Passed'
 * @property {Array}      rules_violations       - Array of NodeResult objects with severity of 'Violation'
 * @property {Array}      rules_manual_checks    - Array of NodeResult objects with severity of 'Manual Check'
 * @property {Array}      rules_warnings         - Array of NodeResult objects with severity of 'Warning'
 * @property {Array}      rules_hidden           - Array of NodeResult objects with severity of 'Hidden'
 *
 * @param {DOM node Object}    node            - The DOM text node 
 * @param {DOMElement Object}  parent_element  - DOMElement object that is the parent DOMElement object in the tree
 */

OpenAjax.a11y.cache.DOMElement = function (node, parent_dom_element) {

  function addAriaAttribute (name, value) {
  
    function getTokens(list) {
    
      var str = "";
      var last = list.length - 1;
      
      for (var i = 0; i < list.length; i++) {
         str += list[i];
         if (i !== last) str += " | ";
      }
    
      return str;
    }
  
    function validValue(value, type, values) {
     
      var i;
      var j;
     
      switch (type) {
       
      case 'boolean':
        if (value === 'true' || value === 'false') return true;
        break;
         
      case 'decimal':
        if (typeof parseFloat(value) === 'number') return true;
        return true;
        break;
         
      case 'idref':
        if (value.length) return true;
        break;
         
      case 'idrefs':
        if (value.length) return true;
        break;
         
      case 'integer':
        if (typeof parseInt(value, 10) === 'number') return true;
        break;
         
      case 'nmtoken':
        for (i = 0; i < values.length; i++) {
          if (value === values[i]) return true; 
        }  
        break;
         
      case 'nmtokens':
        var tokens = [];
        tokens.push(value);
        if (value.indexOf(' ') > 0) tokens = value.split(' ');
        
        var flag = true;
         
        for (i = 0; i < tokens.length && flag; i++) {
          flag = false;
          for (j = 0; j < values.length && !flag; j++) {
            if (tokens[i] === values[j]) flag = true;
          }  
        }
        return flag;
        break;
         
      case 'string':
        if (value.length) return true;
        break;
       
      default:
        break;
       
      }
       
      return false;

    }   // end addAriaAttribute function
     
    var property_info = OpenAjax.a11y.aria.propertyDataTypes[name];

    var av = {};
    av.name = attr.name;
    av.value = attr.value;
    av.type = "undefined";
    av.is_valid_attribute = true;
    av.is_value_valid     = false;
    av.tokens = null;
     
    if (property_info) {
      av.type = property_info.type;
      if (property_info.values) av.tokens = getTokens(property_info.values);
      if (property_info.type === 'boolean') av.tokens = "true | false";
      
      if (typeof property_info.values !== 'undefined') av.is_value_valid = validValue(av.value, av.type, property_info.values);
      else av.is_value_valid = validValue(av.value, av.type, []);
    }
    else {
      av.is_valid_attribute = false;
      invalid_aria_attributes.push(av);
    }

    aria_attributes.push(av);

  }

  var i;
  var attr;
  var attributes;
  var attributes_len;
  var av_object;

  // check to make sure it is a valid node
  if (node === null) return null;

  this.has_element_children = false;
 
  this.type           = Node.ELEMENT_NODE;
  this.document_order = 0;
  this.node           = node;
  this.tag_name       = node.tagName.toLowerCase();
  this.id             = node.id;
 
  if (!this.id || this.id.length === 0) {
    this.id_unique  = OpenAjax.a11y.ID.NOT_DEFINED;
  }
  else {
    this.id_unique  = OpenAjax.a11y.ID.UNIQUE;  
  }
 
  this.character_count = 0;

  // Save relationships with other elements
  this.parent_element = parent_dom_element;
  this.child_dom_elements = [];
  
  var aria_attributes = [];
  var invalid_aria_attributes = []; 
  
  this.parent_landmark = null;

  // Cache important attributes for accessibility
  i = 0;
  attr = null;
  attributes = node.attributes;
  attributes_len = attributes.length;

  this.className = "";
  this.has_alt_attribute    = false;
  this.has_aria_describedby = false;

//  this.tab_index = node.tabIndex;
  this.tab_index = node.getAttribute('tabindex');
  this.has_role = false;  
  this.is_implied_role = false;
  this.has_owns = false;  
  this.has_aria_attributes = false;
    
  this.has_activedescendant = false;
  this.ancestor_has_activedescendant = false;
  if (parent_dom_element) this.ancestor_has_activedescendant = parent_dom_element.ancestor_has_activedescendant;

  this.is_interactive = false;
  this.draggable = undefined;

  this.is_widget = false;
  this.is_landmark = false;
  this.is_live = false;
  this.is_section = false;
  this.is_abstract = false;
  this.is_presentation = false;

  this.role_info = null;

  var role_info;

  for (i = 0; i < attributes.length; i++) {

    attr = attributes[i];

    switch (attr.name) {

    case 'draggable':
      this.draggable = attr.value.toLowerCase();
      break;

    case 'class':
      this.class_name = attr.value.toLowerCase();
      break;

    case 'role':
      
      var role = attr.value.toLowerCase();

      this.has_role = true;
      this.role = role;
    
      role_info = OpenAjax.a11y.aria.getRoleObject(role);
      
      if (!role_info || !role_info.roleType) continue;

      this.role_info = role_info;

      switch (role_info.roleType) {
    
      case 'widget':
        this.is_interactive = true;
        this.is_widget = true;
        this.has_range = role_info.hasRange;
        this.is_tab_stoppable = true;
        if (role_info.container && role_info.container.length) this.is_tab_stoppable = false;
        break;
      
      case 'landmark':
        this.is_landmark = true;
        break;
      
      case 'live':
        this.is_live = true;
        break;
      
      case 'abstract':
        this.is_abstract  = true;
        break;
      
      case 'section':
        this.is_section  = true;
        break;
      
      case 'presentation':
        this.is_presentation = true;
        break;
      
      default:
        break;
    
      } // end switch

      break;

    case 'alt':
      this.alt = attr.value;
      this.has_alt_attribute = true;
      break;

    case 'title':
      this.title = attr.value;
      break;

    case 'aria-activedescendant':
      this.has_activedescendant = true;
      addAriaAttribute('aria-activedescendant', attr.value);
      break;
       
    case 'aria-describedby':
      this.has_aria_describedby = true;
      this.aria_describedby = attr.value;
      addAriaAttribute('aria-describedby', attr.value);
      this.has_aria_attributes = true;
      break;

    case 'aria-hidden':
      this.aria_hidden = attr.value.toLowerCase();
      addAriaAttribute('aria-hidden', attr.value);
      this.has_aria_attributes = true;
      break;

    case 'aria-label':
      this.aria_label = attr.value;
      addAriaAttribute('aria-label', attr.value);
      this.has_aria_attributes = true;
      break;

    case 'aria-labelledby':
      this.aria_labelledby  = attr.value;
      addAriaAttribute('aria-labelledby', attr.value);
      this.has_aria_attributes = true;
      break;

    case 'aria-live':
      this.is_live  = true;
      addAriaAttribute('aria-live', attr.value);
      this.has_aria_attributes = true;
      break;

    case 'aria-owns':
      this.has_owns  = true;
      this.aria_owns = attr.value;
      addAriaAttribute('aria-owns', attr.value);
      break;
      
    default:

      if (attr.name.indexOf('aria-') === 0 ) {
        addAriaAttribute(attr.name, attr.value);
        this.has_aria_attributes = true;
      }
      break;

    } // end switch
  } // end loop0


  this.aria_attributes                     = aria_attributes;
  this.invalid_aria_attributes             = invalid_aria_attributes;

  this.events = {};
  this.events.supports_events = false;
  
  switch (OpenAjax.a11y.EVENT_HANDLER_PROCESSOR) {
  
  case 'firefox':
    this.events = this.EnumerateFirefoxEvents(node, parent_dom_element);
    break;

  case 'fae-util':
    this.events = this.EnumerateFaeUtilEvents(node, parent_dom_element);
    break;

  default:
    break;
  }


  // Create areas to store rule results associates with this node
  this.has_rule_results = false;
  this.rules_violations                = [];
  this.rules_manual_checks             = [];
  this.rules_warnings                  = [];
  this.rules_passed                    = [];
  this.rules_page                      = [];
  this.rules_hidden                    = [];

  return this;

};

/**
 * @method setImpliedRole
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc  Sets an implid role for the DOM element
 *
 * @param  {String} role - new role for the element
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.setImpliedRole = function (role) {

  if (!this.has_role && typeof role === 'string' && (role.length > 0)) {

    role_info = OpenAjax.a11y.aria.getRoleObject(role);
    
    if (!role_info) return;

    this.has_role = true;
    this.role = role;
    this.is_implied_role = true;
    
    this.role_info = role_info;

    if (role_info && role_info.roleType) {
      
      switch (role_info.roleType) {
    
      case 'widget':
        this.is_widget = true;
        break;
      
      case 'landmark':
        this.is_landmark = true;
        break;
      
      case 'live':
        this.is_live = true;
        break;
      
      case 'abstract':
        this.is_abstract  = true;
        break;
      
      case 'section':
        this.is_section  = true;
        break;
      
      default:
        break;
   
      } // end switch
    }  
  }
};

/**
 * @method getId
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc   If definded, return the id of the dom node
 *
 * @return {String} If defined return id value, else empty string
 */

OpenAjax.a11y.cache.DOMElement.prototype.getId = function () {

  if (this.id) return this.id;
  
  return "";

};

/**
 * @method getClassName
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc   If defined, return the class attribute value of the dom node
 *
 * @return {String} If defined return class value value, else empty string
 */

OpenAjax.a11y.cache.DOMElement.prototype.getClassName = function () {

  if (this.class_name) return this.class_name;
  
  return "";

};


/**
 * @method getParentLandmark
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc   If defined, return the parent landmark element information
 *
 * @return {String} If defined return class value value, else empty string
 */

OpenAjax.a11y.cache.DOMElement.prototype.getParentLandmark = function () {

  if (this.parent_landmark) return this.parent_landmark.toString();
  
  return "none";

};


/**
 * @method hasAttrWithValue
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc   Check DOMElement for presence of attribute with specified value
 *
 * @param  {String} name  - name of attribute
 * @param  {String} value - value of attribute
 *
 * @return {boolean} Indicates whether or not DOMElement has the specified
 *                   attribute with the specified value.
 */

OpenAjax.a11y.cache.DOMElement.prototype.hasAttrWithValue = function (name, value) {

  if (this.hasOwnProperty (name)) {
    return this[name] === value;
  }

  return false;

};

/**
 * @method hasOwns
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc   Check DOMElement for presence of aria-owns attribute
 *
 * @return {boolean} Indicates whether or not DOMElement has aria-owns attribute with values
 */

OpenAjax.a11y.cache.DOMElement.prototype.hasOwns = function () {

  return this.has_owns;

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.DOMElement.prototype.getNodeResults = function () {
 
  function addResultNodes(items) {
  
    var len = items.length;
    
    for (var i = 0; i < len; i++ ) {
      result_nodes.push(items[i]);
    }
    
  }

  var result_nodes = [];
  
  addResultNodes(this.rules_violations);
  addResultNodes(this.rules_manual_checks);
  addResultNodes(this.rules_warnings);
  addResultNodes(this.rules_passed);
  addResultNodes(this.rules_page);
  addResultNodes(this.rules_hidden);
  
  return result_nodes;
  
};

/**
 * @method containsInteractiveElements
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Tests if a DOM element has any descendants that are widgets 
 *
 * @return {Boolean}  'True' if at least one descendant dom element is a widget, otherwise 'false'
 */

OpenAjax.a11y.cache.DOMElement.prototype.containsInteractiveElements = function () {

  function checkChildren(children) {
  
     if ((typeof children != 'object') || !children.length ) return false;
     
     var flag = false;
     
     for (var i = 0; (i < children.length) && !flag; i++) {
     
       var child = children[i];
     
       if (child.type != Node.ELEMENT_NODE) continue;
     
       if (child.is_interactive) flag = true;
       else if (child.child_dom_elements.length) flag = checkChildren(child.child_dom_elements);
     
     }
     
     return flag;
  
  }

  return checkChildren(this.child_dom_elements);

};


/**
 * @method hasParentRole
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Tests if a widget has a parent element with a certain role
 *
 * @param {String}  role -  Role to find 
 *
 * @return {Boolean} Returns true if widget has child element with role, otherwise false
 */

OpenAjax.a11y.cache.DOMElement.prototype.hasParentRole = function (role) {

   function checkParentElementForRole(dom_element) {

     if (!dom_element) return false;

     if (dom_element.role === role) {
       return true;
     }
     else {
       return checkParentElementForRole(dom_element.parent_element);     
     }
     
   }

   return checkParentElementForRole(this.parent_element);
  
};

/**
 * @method getHasDescribedBy
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns NLS string on whether the dom element has a aria-describedby attribute  
 *
 * @return {String} If true returns "Yes", else "No"
 */

OpenAjax.a11y.cache.DOMElement.prototype.getHasDescribedBy = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;

  if (this.has_aria_describedby) return cache_nls.getLabelAndValueNLS('has_aria_describedby', this.has_aria_describedby).value;

  return "";
};

/**
 * @method getAccessibility
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns the worst severity level of rule results  
 *
 * @return {Object} Results an object wiith two properties: 'severity' : nls value of the severity, 'style' : a severity styling constant
 */

OpenAjax.a11y.cache.DOMElement.prototype.getAccessibility = function () {
   
  var cache_nls      = OpenAjax.a11y.cache_nls;
  var RESULT_VALUE       = OpenAjax.a11y.RESULT_VALUE;

  var severity = cache_nls.getResultValueNLS(RESULT_VALUE.NONE); 
  a.label    = severity.label;

  if (this.rules_hidden.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.HIDDEN);
  }

  if (this.rules_page.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.PAGE);
  }
  
  if (this.rules_passed.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.PASS);
  }

  if (this.rules_manual_checks.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.MANUAL_CHECK);
  }

  if (this.rules_warnings.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.WARNING);
  }
  
  if (this.rules_violations.length) {
    severity = cache_nls.getResultValueNLS(RESULT_VALUE.VIOLATION);
  }

  return severity;
  
};


/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.DOMElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var av_object;
 
  var attributes  = [];

  if (this.tag_name === 'img'  || 
      this.tag_name === 'area' || 
      this.tag_name === 'applet') cache_nls.addPropertyIfDefined(attributes, this, 'alt');

  if (this.id.length) cache_nls.addPropertyIfDefined(attributes, this, 'id');  
  cache_nls.addPropertyIfDefined(attributes, this, 'tab_index');  
  cache_nls.addPropertyIfDefined(attributes, this, 'class_name');
  cache_nls.addPropertyIfDefined(attributes, this, 'role');
  cache_nls.addPropertyIfDefined(attributes, this, 'draggable');
  
  for (var i = 0; i < this.aria_attributes.length; i++) {
    av = this.aria_attributes[i];
    attributes.push(cache_nls.getLabelAndValueNLS(av.name, av.value));
  }
  
  if (!unsorted) this.sortItems(attributes);
  
  return attributes;

};

/**
 * @method hasEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns if an element has user interface events attached to it
 *
 * @return {Boolean} Returns true if event user interface event handlers are attached to the node, otherwise false
 */

OpenAjax.a11y.cache.DOMElement.prototype.hasEvents = function () {

  var has_events;

  has_events = has_events || this.events.has_load;

  has_events = has_events || this.events.has_change;

  has_events = has_events || this.events.has_click;
  has_events = has_events || this.events.has_double_click;

  has_events = has_events || this.events.has_focus;
  has_events = has_events || this.events.has_blur;

  has_events = has_events || this.events.has_key_down;
  has_events = has_events || this.events.has_key_press;
  has_events = has_events || this.events.has_key_up;

  has_events = has_events || this.events.has_mouse_down;
  has_events = has_events || this.events.has_mouse_up;
  has_events = has_events || this.events.has_mouse_move;
  has_events = has_events || this.events.has_mouse_out;
  has_events = has_events || this.events.has_mouse_over;
  has_events = has_events || this.events.has_mouse_enter;
  has_events = has_events || this.events.has_mouse_leave;

  has_events = has_events || this.events.has_drag;
  has_events = has_events || this.events.has_drag_end;
  has_events = has_events || this.events.has_drag_enter;
  has_events = has_events || this.events.has_drag_leave;
  has_events = has_events || this.events.has_drag_over;
  has_events = has_events || this.events.has_drag_start;
  has_events = has_events || this.events.has_drop;
  
  return has_events;
};


/**
 * @method hasChangeEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns true if the element has change events
 *
 * @param  {Array}   prop_list : If defined, the names of the event handlers defined will be added to the list as property object
 * 
 * @return {Boolean} Returns "True" element has change event
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.hasChangeEvents = function (prop_list) {

  function addEvent(event, name) {

    if (event) {
      has_event = true;
      
      if (typeof prop_list === 'object') {
        var prop = {};
      
        prop.label = name;
        prop.value = 'true';
        prop.description = "";
      
        prop_list.push(prop);
      }  
    }   
  }

  var has_event = false;
  
  addEvent(this.events.has_change,  'onchange');

  return has_event;
  
};


/**
 * @method hasFocusEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns true if the element has focus or blur events
 *
 * @param  {Array}   prop_list : If defined, the names of the event handlers defined will be added to the list as property object
 * 
 * @return {Boolean} Returns "True" element has focus and blur events
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.hasFocusEvents = function (prop_list) {

  function addEvent(event, name) {

    if (event) {
      has_event = true;
      
      if (typeof prop_list === 'object') {
        var prop = {};
      
        prop.label = name;
        prop.value = 'true';
        prop.description = "";
      
        prop_list.push(prop);
      }  
    }   
  }

  var has_event = false;
  
  addEvent(this.events.has_focus,  'onfocus');
  addEvent(this.events.has_blur,   'onblur');

  return has_event;
  
};



/**
 * @method hasClickEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns true if the element has click or double click events
 *
 * @param  {Array}   prop_list : If defined, the names of the event handlers defined will be added to the list as property object
 * 
 * @return {Boolean} Returns "True" element has click and double click events
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.hasClickEvents = function (prop_list) {

  function addEvent(event, name) {

    if (event) {
      has_event = true;
      
      if (typeof prop_list === 'object') {
        var prop = {};
      
        prop.label = name;
        prop.value = 'true';
        prop.description = "";
      
        prop_list.push(prop);
      }  
    }   
  }

  var has_event = false;
  
  addEvent(this.events.has_click,  'onclick');
  addEvent(this.events.has_double_click,    'ondoubleclick');

  return has_event;
  
};


/**
 * @method hasMouseEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns true if the element has mouse specfic event handlers (i.e. up, down, move, over, out)
 *
 * @param  {Array}   prop_list : If defined, the names of the event handlers defined will be added to the list as property object
 * 
 * @return {Boolean} Returns "True" element has mouse up, down, move, over and/or out events
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.hasMouseEvents = function (prop_list) {

  function addEvent(event, name) {

    if (event) {
      has_event = true;
      
      if (typeof prop_list === 'object') {
        var prop = {};
      
        prop.label = name;
        prop.value = 'true';
        prop.description = "";
      
        prop_list.push(prop);
      }  
    }   
  }

  var has_event = false;
  
  addEvent(this.events.has_mouse_down,  'onmousedown');
  addEvent(this.events.has_mouse_up,    'onmouseup');
  addEvent(this.events.has_mouse_move,  'onmousemove');
  addEvent(this.events.has_mouse_out,   'onmouseout');
  addEvent(this.events.has_mouse_over,  'onmouseover');
  addEvent(this.events.has_mouse_enter, 'onmouseenter');
  addEvent(this.events.has_mouse_leave, 'onmouseleave');

  return has_event;
  
};

/**
 * @method hasDragEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns true if the element has drag specfic event handlers 
 *
 * @param  {Array}   prop_list : If defined, the names of the event handlers defined will be added to the list as property object
 * 
 * @return {Boolean} Returns true if element has drag events. otherwise false
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.hasDragEvents = function (prop_list) {

  function addEvent(event, name) {
  
//     OpenAjax.a11y.logger.debug("   " + name + "=" + event);

    if (event) {
      has_event = true;
      
      if (typeof prop_list === 'object') {
        var prop = {};
      
        prop.label = name;
        prop.value = 'true';
        prop.description = "";
      
        prop_list.push(prop);
      }  
    }   
  }

  var has_event = false;
  
  var de = this;

//  OpenAjax.a11y.logger.debug("DRAG: " + de.toString());

  addEvent(this.events.has_drag,       'ondrag');
  addEvent(this.events.has_drag_end,   'ondragend');
  addEvent(this.events.has_drag_enter, 'ondragenter');
  addEvent(this.events.has_drag_leave, 'ondragleave');
  addEvent(this.events.has_drag_over,  'ondragover');
  addEvent(this.events.has_drag_start, 'ondragstart');
  addEvent(this.events.has_drop,       'ondrop');

  return has_event;
  
};


/**
 * @method hasKeyboardEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns true if the element has keyboard specfic event handlers 
 *
 * @param  {Array}   prop_list : If defined, the names of the event handlers defined will be added to the list as property object
 * 
 * @return {Boolean} Returns true if element has keyboard events, otherwise false
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.hasKeyboardEvents = function (str, prop_list) {

  function addEvent(event, name) {

    if (event) {
      has_event = true;
      
      if (typeof prop_list === 'object') {
        var prop = {};
      
        prop.label = name;
        prop.value = 'true';
        prop.description = "";
      
        prop_list.push(prop);
      }  
    }   
  }

  var has_event = false;
  
  addEvent(this.events.has_key_down,  'onkeydown');
  addEvent(this.events.has_key_press, 'onkeypress');
  addEvent(this.events.has_key_up,    'onkeyup');

  return has_event;
  
};




/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns an array of objects representing events associated with the element 
 *
 * @return {Array} Returns a array of event object results
 */

OpenAjax.a11y.cache.DOMElement.prototype.getEvents = function () {

  function addHasEvent(event_type, event_on_element, event_on_ancestor) {
  
    var o = {};
    
    o.label = event_type;
    o.event_on_element        = nls_false;
    o.event_on_element_style  = "no";
    o.event_on_ancestor       = nls_false;
    o.event_on_ancestor_style = "no";

    if (event_on_element) {
      o.event_on_element        = nls_true;
      o.event_on_element_style  = "yes";
    }
    
    if (event_on_ancestor) {
      o.event_on_ancestor       = nls_true;
      o.event_on_ancestor_style = "yes";
    }  

    events.push(o);
  
  }

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var nls_false = cache_nls.getCacheNLS().boolean_values.false_value;
  var nls_true  = 'true';
 
  var events = [];
  
  addHasEvent('blur',          this.events.has_blur,          this.events.ancestor_has_blur);
  addHasEvent('focus',         this.events.has_focus,         this.events.ancestor_has_focus);
  
  addHasEvent('click',         this.events.has_click,         this.events.ancestor_has_click);
  addHasEvent('double click',  this.events.has_double_click,  this.events.ancestor_has_double_click);

  addHasEvent('key down',      this.events.has_key_down,      this.events.ancestor_has_key_down);
  addHasEvent('key press',     this.events.has_key_press,     this.events.ancestor_has_key_press);
  addHasEvent('key down',      this.events.has_key_up,        this.events.ancestor_has_key_up);

  addHasEvent('mouse down',    this.events.has_mouse_down,    this.events.ancestor_has_mouse_down);
  addHasEvent('mouse up',      this.events.has_mouse_up,      this.events.ancestor_has_mouse_up);
  addHasEvent('mouse move',    this.events.has_mouse_move,    this.events.ancestor_has_mouse_move);

  addHasEvent('mouse leave',    this.events.has_mouse_leave,    this.events.ancestor_has_mouse_leave);
  addHasEvent('mouse enter',    this.events.has_mouse_enter,    this.events.ancestor_has_mouse_enter);
  
  addHasEvent('mouse out',     this.events.has_mouse_out,     this.events.ancestor_has_mouse_out);
  addHasEvent('mouse over',    this.events.has_mouse_over,    this.events.ancestor_has_mouse_over);
  
  addHasEvent('drag',        this.events.has_drag,       this.events.ancestor_has_drag);
  addHasEvent('drag end',    this.events.has_drag_end,   this.events.ancestor_has_drag_end);
  addHasEvent('drag enter',  this.events.has_drag_enter, this.events.ancestor_has_drag_enter);
  addHasEvent('drag leave',  this.events.has_drag_leave, this.events.ancestor_has_drag_leave);
  addHasEvent('drag over',   this.events.has_drag_over,  this.events.ancestor_has_drag_over);
  addHasEvent('drag start',  this.events.has_drag_start, this.events.ancestor_has_drag_start);
  addHasEvent('drop',        this.events.has_drop,       this.events.ancestor_has_drag_drop);
  
  addHasEvent('change',        this.events.has_change,        this.events.ancestor_has_change);

  return events;

};


/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns an array of styling information for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of NLS objects for styling
 */

OpenAjax.a11y.cache.DOMElement.prototype.getStyle = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
 
  var properties  = [];

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'is_visible_onscreen');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'is_visible_to_at');

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'display');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'visibility');
  
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'color');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'opacity');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_color');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_image');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_repeat');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'background_position');

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'font_family');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'font_size');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'font_weight');

  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'position');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'left');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'top');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'width');
  cache_nls.addPropertyIfDefined(properties, this.computed_style, 'height');
  
  return properties;

};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns an array of styling information for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of NLS objects for styling
 */

OpenAjax.a11y.cache.DOMElement.prototype.getCacheProperties = function () {

  var i;

  var cache_nls = OpenAjax.a11y.cache_nls;
 
  var properties  = [];

  cache_nls.addPropertyIfDefined(properties, this, 'id_unique');
  cache_nls.addPropertyIfDefined(properties, this, 'xpath');
  cache_nls.addPropertyIfDefined(properties, this, 'character_count');
  
  cache_nls.addPropertyIfDefined(properties, this, 'calculated_aria_description');

  cache_nls.addPropertyIfDefined(properties, this, 'document_order');

  cache_nls.addPropertyIfDefined(properties, this, 'parent_landmark');

  cache_nls.addPropertyIfDefined(properties, this, 'is_interactive');
  cache_nls.addPropertyIfDefined(properties, this, 'is_widget');
  cache_nls.addPropertyIfDefined(properties, this, 'is_landmark');
  cache_nls.addPropertyIfDefined(properties, this, 'is_live');
  
  cache_nls.addPropertyIfDefined(properties, this, 'has_rule_results');
  cache_nls.addPropertyIfDefined(properties, this, 'has_role');
  cache_nls.addPropertyIfDefined(properties, this, 'is_implied_role');

  cache_nls.addPropertyIfDefined(properties, this, 'has_activedescendant');
  cache_nls.addPropertyIfDefined(properties, this, 'is_owned');

  var invalid_aria_attributes     = this.invalid_aria_attributes;
  var invalid_aria_attributes_len = invalid_aria_attributes.length;

  for (i = 0; i < invalid_aria_attributes_len; i++) {
    cache_nls.addInvalidAttribute(properties, invalid_aria_attributes[i]);
  }
  
  return properties;

};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number | Object} Returns the value of the property
 */

OpenAjax.a11y.cache.DOMElement.prototype.getCachePropertyValue = function (property) {

//  OpenAjax.a11y.logger.debug("dom element property: " + property + " value= " + this[property]);

  if (typeof this[property] === 'undefined') {
     if(typeof this.computed_style[property] === 'undefined') {
       if(typeof this.events[property] === 'undefined') {
         for (var i = 0; i < this.aria_attributes.length; i++) {
            var attr = this.aria_attributes[i];
            if (attr.name === property) return attr.value;
         }
         return null;
       }
       else {
         return this.events[property]; 
       }   
     }
     else {
       return this.computed_style[property]; 
     }
  }
  
  return this[property];
  
};


/**
 * @method sortItems
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.DOMElement.prototype.sortItems = function (items) {

  var swapped = false;
  var temp = null;
  var i;
  var items_len = items.length;

  do{
    swapped = false;
    for (i = 1; i < items_len; i++ ) {
     if (items[i-1].label.toLowerCase() > items[i].label.toLowerCase()) {
      // swap the values
      temp = items[i-1];
      items[i-1] = items[i];
      items[i] = temp;
      swapped = true;
     }
    } // end loop
  } while (swapped);

};

/** 
 * @method EnumerateFirefoxEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc  Finds the event information of the node for a DOMElement object
 *
 * @param  {Object}     node              - Object The DOM element node that corresponds
 *                                          to this DOMElement object, and from which
 *                                          common information is derived for the DOM
 *                                          element cache.
 *
 * @param  {DOMElement} parent_dom_element - Parent DOMElement object associated with the node's parent node 
 *
 * @return {Object}  Returns an object with event information
 */

OpenAjax.a11y.cache.DOMElement.prototype.EnumerateFirefoxEvents = function (node, parent_dom_element) {

  var i;
 
  var events = {};
  events.supports_events = false;

  var event_listener = Components.classes["@mozilla.org/eventlistenerservice;1"];

  if (event_listener !== null &&
    event_listener !== undefined) {

    events.supports_events = true;

    events.has_blur         = false;
    events.has_change       = false;
    events.has_click        = false;
    events.has_double_click = false;
    events.has_focus        = false;
    events.has_key_down     = false;
    events.has_key_press    = false;
    events.has_key_up       = false;
    events.has_load         = false;
    events.has_mouse_down   = false;
    events.has_mouse_up     = false;
    events.has_mouse_move   = false;
    events.has_mouse_out    = false;
    events.has_mouse_over   = false;
    events.has_mouse_enter  = false;
    events.has_mouse_leave  = false;

    events.has_drag       = false;
    events.has_drag_end   = false;
    events.has_drag_enter = false;
    events.has_drag_leave = false;
    events.has_drag_over  = false;
    events.has_drag_start = false;
    events.has_drop       = false;

    if (parent_dom_element && parent_dom_element.events) {
      events.ancestor_has_blur         = parent_dom_element.events.has_blur         || parent_dom_element.events.ancestor_has_blur;
      events.ancestor_has_change       = parent_dom_element.events.has_change       || parent_dom_element.events.ancestor_has_change;
      events.ancestor_has_click        = parent_dom_element.events.has_click        || parent_dom_element.events.ancestor_has_click;
      events.ancestor_has_double_click = parent_dom_element.events.has_double_click || parent_dom_element.events.ancestor_has_double_click;
      events.ancestor_has_focus        = parent_dom_element.events.has_focus        || parent_dom_element.events.ancestor_has_focus;
      events.ancestor_has_key_down     = parent_dom_element.events.has_key_down     || parent_dom_element.events.ancestor_has_key_down;
      events.ancestor_has_key_press    = parent_dom_element.events.has_key_press    || parent_dom_element.events.ancestor_has_key_press;
      events.ancestor_has_key_up       = parent_dom_element.events.has_key_up       || parent_dom_element.events.ancestor_has_key_up;
      events.ancestor_has_load         = parent_dom_element.events.has_load         || parent_dom_element.events.ancestor_has_load;
      
      events.ancestor_has_mouse_down   = parent_dom_element.events.has_mouse_down   || parent_dom_element.events.ancestor_has_mouse_down;
      events.ancestor_has_mouse_up     = parent_dom_element.events.has_mouse_up     || parent_dom_element.events.ancestor_has_mouse_up;
      events.ancestor_has_mouse_move   = parent_dom_element.events.has_mouse_move   || parent_dom_element.events.ancestor_has_mouse_move;
      events.ancestor_has_mouse_out    = parent_dom_element.events.has_mouse_out    || parent_dom_element.events.ancestor_has_mouse_out;
      events.ancestor_has_mouse_over   = parent_dom_element.events.has_mouse_over   || parent_dom_element.events.ancestor_has_mouse_over;
      events.ancestor_has_mouse_enter  = parent_dom_element.events.has_mouse_enter  || parent_dom_element.events.ancestor_has_mouse_enter;
      events.ancestor_has_mouse_leave  = parent_dom_element.events.has_mouse_leave  || parent_dom_element.events.ancestor_has_mouse_leave;

      events.ancestor_has_drag       = parent_dom_element.events.has_drag       || parent_dom_element.events.ancestor_has_drag;
      events.ancestor_has_drag_end   = parent_dom_element.events.has_drag_end   || parent_dom_element.events.ancestor_has_drag_end;
      events.ancestor_has_drag_enter = parent_dom_element.events.has_drag_enter || parent_dom_element.events.ancestor_has_drag_enter;
      events.ancestor_has_drag_leave = parent_dom_element.events.has_drag_leave || parent_dom_element.events.ancestor_has_drag_leave;
      events.ancestor_has_drag_over  = parent_dom_element.events.has_drag_over  || parent_dom_element.events.ancestor_has_drag_over;
      events.ancestor_has_drag_start = parent_dom_element.events.has_drag_start || parent_dom_element.events.ancestor_has_drag_start;
      events.ancestor_has_drop       = parent_dom_element.events.has_drop       || parent_dom_element.events.ancestor_has_drop;

    }
    else {
      events.ancestor_has_blur         = false;
      events.ancestor_has_change       = false;
      events.ancestor_has_click        = false;
      events.ancestor_has_double_click = false;
      events.ancestor_has_focus        = false;
      events.ancestor_has_key_down     = false;
      events.ancestor_has_key_press    = false;
      events.ancestor_has_key_up       = false;
      events.ancestor_has_load         = false;
      
      events.ancestor_has_mouse_down   = false;
      events.ancestor_has_mouse_up     = false;
      events.ancestor_has_mouse_move   = false;
      events.ancestor_has_mouse_out    = false;
      events.ancestor_has_mouse_over   = false;
      events.ancestor_has_mouse_enter  = false;
      events.ancestor_has_mouse_leave  = false;

      events.ancestor_has_drag       = false;
      events.ancestor_has_drag_end   = false;
      events.ancestor_has_drag_enter = false;
      events.ancestor_has_drag_leave = false;
      events.ancestor_has_drag_over  = false;
      events.ancestor_has_drag_start = false;
      events.ancestor_has_drop       = false;

   }

   var event_listener_service = event_listener.createInstance(Components.interfaces.nsIEventListenerService);
   var node_event_service     = event_listener_service.getListenerInfoFor(node, {});

   for (i = 0; i < node_event_service.length; i++) {
     var node_event_information = node_event_service[i].QueryInterface(Components.interfaces.nsIEventListenerInfo);
     
//     OpenAjax.a11y.logger.debug("Event Info: type=" + node_event_information.type + " system event=" + node_event_information.inSystemEventGroup + " allows untrusted=" + node_event_information.allowsUntrusted);

       switch (node_event_information.type) {

       case "blur":
         events.has_blur = true;
         break;

       case "change":
         events.has_change = true;
         break;

       case "click":
         events.has_click = true;
         break;

       case "dbclick":
         events.has_double_click = true;
         break;

       case "focus":
         events.has_focus   = true;
         break;

       case "keydown":
         events.has_key_down  = true;
         break;

       case "keypress":
         events.has_key_press = true;
         break;

       case "keyup":
         events.has_key_up   = true;
         break;

       case "load":
         events.has_load    = true;
         break;

       case "mousedown":
         events.has_mouse_down = true;
         break;

       case "mouseup":
         events.has_mouse_up  = true;
         break;

       case "mousemove":
         events.has_mouse_move = true;
         break;

       case "mouseout":
         events.has_mouse_out = true;
         break;

       case "mouseover":
         events.has_mouse_over = true;
         break;

       case "mouseenter":
         events.has_mouse_enter = true;
         break;

       case "mouseleave":
         events.has_mouse_leave = true;
         break;

       case "drag":
         events.has_drag = true;
         break;

       case "dragend":
         events.has_drag_end = true;
         break;

       case "dragenter":
         events.has_drag_enter = true;
         break;
         
       case "dragleave":
         events.has_drag_leave = true;
         break;
         
       case "dragover":
         events.has_drag_over = true;
         break;
         
       case "dragstart":
         events.has_drag_start = true;
         break;
         
       case "drop":
         events.has_drop = true;
         break;
         
       default:
         break;

       } // endswitch
   } // end loop
 }

 return events;

};

/** 
 * @method EnumerateFaeUtilEvents
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc  Finds the event information of the node for a DOMElement object
 *
 * @param  {Object}     node              - Object The DOM element node that corresponds
 *                                          to this DOMElement object, and from which
 *                                          common information is derived for the DOM
 *                                          element cache.
 *
 * @param  {DOMElement} parent_dom_element - Parent DOMElement object associated with the node's parent node 
 *
 * @return {Object}  Returns an object with event information
 */

OpenAjax.a11y.cache.DOMElement.prototype.EnumerateFaeUtilEvents = function (node, parent_dom_element) {

  function testForEvent(e) {
  
    var has_event = node.getAttribute(e);
    
    if (has_event == 'true') {
      events.supports_events = true;
      return true;  
    }  
    
    return false;

  }

  var events = {};

  events.ancestor_has_blur         = false;
  events.ancestor_has_change       = false;
  events.ancestor_has_click        = false;
  events.ancestor_has_double_click = false;
  events.ancestor_has_focus        = false;
  events.ancestor_has_key_down     = false;
  events.ancestor_has_key_press    = false;
  events.ancestor_has_key_up       = false;
  events.ancestor_has_load         = false;
      
  events.ancestor_has_mouse_down   = false;
  events.ancestor_has_mouse_up     = false;
  events.ancestor_has_mouse_move   = false;
  events.ancestor_has_mouse_out    = false;
  events.ancestor_has_mouse_over   = false;
  events.ancestor_has_mouse_enter  = false;
  events.ancestor_has_mouse_leave  = false;

  events.ancestor_has_drag       = false;
  events.ancestor_has_drag_end   = false;
  events.ancestor_has_drag_enter = false;
  events.ancestor_has_drag_leave = false;
  events.ancestor_has_drag_over  = false;
  events.ancestor_has_drag_start = false;
  events.ancestor_has_drop       = false;
  
  events.has_blur         = testForEvent('oaa-has-blur');
  events.has_change       = testForEvent('oaa-has-change');
  events.has_click        = testForEvent('oaa-has-click');
  events.has_double_click = testForEvent('oaa-has-double_click');
  events.has_focus        = testForEvent('oaa-has-focus');
    
  events.has_key_down     = testForEvent('oaa-has-key_down');
  events.has_key_press    = testForEvent('oaa-has-key_press');
  events.has_key_up       = testForEvent('oaa-has-key_up');
    
  events.has_load         = testForEvent('oaa-has-load');
    
  events.has_mouse_down   = testForEvent('oaa-has-mouse_down');
  events.has_mouse_up     = testForEvent('oaa-has-mouse_up');
  events.has_mouse_move   = testForEvent('oaa-has-mouse_move');
  events.has_mouse_out    = testForEvent('oaa-has-mouse_out');
  events.has_mouse_over   = testForEvent('oaa-has-mouse_over');
    
  events.has_mouse_enter  = testForEvent('oaa-has-mouse_enter');
  events.has_mouse_leave  = testForEvent('oaa-has-mouse_leave');

  events.has_drag       = testForEvent('oaa-has-drag');
  events.has_drag_end   = testForEvent('oaa-has-drag_end');
  events.has_drag_enter = testForEvent('oaa-has-drag_enter');
  events.has_drag_leave = testForEvent('oaa-has-drag_leave');
  events.has_drag_over  = testForEvent('oaa-has-over');
  events.has_drag_start = testForEvent('oaa-has-start');
  events.has_drop       = testForEvent('oaa-has-drop');

  if (parent_dom_element && parent_dom_element.events) {
    events.ancestor_has_blur         = parent_dom_element.events.has_blur         || parent_dom_element.events.ancestor_has_blur;
    events.ancestor_has_change       = parent_dom_element.events.has_change       || parent_dom_element.events.ancestor_has_change;
    events.ancestor_has_click        = parent_dom_element.events.has_click        || parent_dom_element.events.ancestor_has_click;
    events.ancestor_has_double_click = parent_dom_element.events.has_double_click || parent_dom_element.events.ancestor_has_double_click;
    events.ancestor_has_focus        = parent_dom_element.events.has_focus        || parent_dom_element.events.ancestor_has_focus;
    events.ancestor_has_key_down     = parent_dom_element.events.has_key_down     || parent_dom_element.events.ancestor_has_key_down;
    events.ancestor_has_key_press    = parent_dom_element.events.has_key_press    || parent_dom_element.events.ancestor_has_key_press;
    events.ancestor_has_key_up       = parent_dom_element.events.has_key_up       || parent_dom_element.events.ancestor_has_key_up;
    events.ancestor_has_load         = parent_dom_element.events.has_load         || parent_dom_element.events.ancestor_has_load;
      
    events.ancestor_has_mouse_down   = parent_dom_element.events.has_mouse_down   || parent_dom_element.events.ancestor_has_mouse_down;
    events.ancestor_has_mouse_up     = parent_dom_element.events.has_mouse_up     || parent_dom_element.events.ancestor_has_mouse_up;
    events.ancestor_has_mouse_move   = parent_dom_element.events.has_mouse_move   || parent_dom_element.events.ancestor_has_mouse_move;
    events.ancestor_has_mouse_out    = parent_dom_element.events.has_mouse_out    || parent_dom_element.events.ancestor_has_mouse_out;
    events.ancestor_has_mouse_over   = parent_dom_element.events.has_mouse_over   || parent_dom_element.events.ancestor_has_mouse_over;
    events.ancestor_has_mouse_enter  = parent_dom_element.events.has_mouse_enter  || parent_dom_element.events.ancestor_has_mouse_enter;
    events.ancestor_has_mouse_leave  = parent_dom_element.events.has_mouse_leave  || parent_dom_element.events.ancestor_has_mouse_leave;

    events.ancestor_has_drag       = parent_dom_element.events.has_drag       || parent_dom_element.events.ancestor_has_drag;
    events.ancestor_has_drag_end   = parent_dom_element.events.has_drag_end   || parent_dom_element.events.ancestor_has_drag_end;
    events.ancestor_has_drag_enter = parent_dom_element.events.has_drag_enter || parent_dom_element.events.ancestor_has_drag_enter;
    events.ancestor_has_drag_leave = parent_dom_element.events.has_drag_leave || parent_dom_element.events.ancestor_has_drag_leave;
    events.ancestor_has_drag_over  = parent_dom_element.events.has_drag_over  || parent_dom_element.events.ancestor_has_drag_over;
    events.ancestor_has_drag_start = parent_dom_element.events.has_drag_start || parent_dom_element.events.ancestor_has_drag_start;
    events.ancestor_has_drop       = parent_dom_element.events.has_drop       || parent_dom_element.events.ancestor_has_drop;

  }

  return events;

};


/**
 * @method addChild
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc    Adds a DOMElement or DOMText object to the tree of DOM text/elements  
 *
 * @param  {DOMElement | DOMText} child_object  - DOMElement or DOMText object 
 *
 * @return  Nothing 
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.addChild = function ( child_object ) {

 if (child_object) {
  this.child_dom_elements.push(child_object);
 }

};

/**
 * @method addToCharacterCount
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc  Adds to the current character count of the text content of the
 *          contained in the DOMelement and its immediate children
 *
 * @param {Number} length - Number to add to the character count
 *
 * @return Nothing
 */

OpenAjax.a11y.cache.DOMElement.prototype.addToCharacterCount = function ( length ) {

 this.character_count += length;

};

/**
 * @method addComputedStyle
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc    Adds computed style information to the DOMElement object and
 *          calculate the color contrast ratio
 *
 * @param  {DOMElement} parent_element  - The parent DOMElement object, used
 *                                        for information about inherited style
 *                                        information
 *
 * @return Nothing
 */

OpenAjax.a11y.cache.DOMElement.prototype.addComputedStyle = function (parent_element) {

 if (this.tag_name == 'iframe' || this.tag_name == 'frame' || this.tag_name == 'body')
   this.computed_style = new OpenAjax.a11y.cache.DOMElementComputedStyle(this, null);
 else this.computed_style = new OpenAjax.a11y.cache.DOMElementComputedStyle(this, parent_element);
   
 
 this.computed_style.calculateColorContrastRatio();
};

/**
 * @method calculateXPath
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc   Calculate the XPath string that uniquely identifies the
 *           DOM node referenced by this DOMElement's node property and
 *           set its xpath property to this calculated value.
 *
 * @param  {DOMElement} parent_element - The parent DOMElement object, used
 *                                       for information for xpath calculation
 *
 * @usage Sets the DOMElement's xpath property
 *
 * @return Nothing 
 */

OpenAjax.a11y.cache.DOMElement.prototype.calculateXPath = function (parent_element) {

 function attributePredicate(attrName, attrValue) {
  return "[@" + attrName + "='" + attrValue + "']";
 }

 this.xpath = "";

 // If a root node ignore calculation
 if (!this.tag_name) {
  return;
 }

 // now build up the XPath using parent, tag_name, id, role and class values
 if (parent_element && parent_element.xpath) {
  this.xpath = parent_element.xpath + "/" + this.tag_name;
 }
 else {
  this.xpath = "/" + this.tag_name;
 }

 if (this.id) {
  this.xpath += attributePredicate("id", this.id);
 }

 if (this.role) {
  this.xpath += attributePredicate("role", this.role);
 }
 else {
  if (this.class_name) {
   this.xpath += attributePredicate("class", this.class_name);
  }
 }

};


/**
 * @method getText
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc Returns text content of a DOMElement, including the ALT text of images
 *       through recursion through the DOMText and DOMElement descendents of
 *       the DOMElement
 *
 * @return {String} Returns the text content of an element and its children
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.getText = function() {

  function getText(dom_element, strings) {
    // If text node get the text and return
    if( dom_element.type == Node.TEXT_NODE ) {
      strings.push( dom_element.text );
    } else {
      // if an element for through all the children elements looking for text
      if( dom_element.type == Node.ELEMENT_NODE ) {
        // check to see if IMG or AREA element and to use ALT content if defined
        if((dom_element.tag_name == 'img') || (dom_element.tag_name == 'area')) {
     
          if (dom_element.alt) {
            strings.push(dom_element.alt);
          } 
          
        } else {
    
          for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
            getText(dom_element.child_dom_elements[i], strings);
          } // end loop
          
        } 
      }
    } 
  } // end function getStrings

 // Create return object
 var str = "";
 var strings = [];

 getText(this, strings); 
 
 if (strings.length) str = strings.join(" ").normalizeSpace();
 
 return str;
 
};

 /**
 * @method getTextObject
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 * 
 * @desc Returns an object with information about the accessible text of a DOMElement object
 *         and its descendents
 *
 * @param {Boolean}  visible  - Optional, if true text must be visible to be included. default false
 *
 * @return {Object}  Returns an object with the following properties: 
 *                     'height', 
 *                     'width',
 *                     'image_count',
 *                     'name',
 *                     'name_from_text_nodes',
 *                     'name_from_image_alt',
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.getTextObject = function(visible) {

  if (typeof visible !== 'boolean') visible = false;

  function getText(dom_element, strings, texts, alts) {
    // If text node get the text and return
    if( dom_element.type == Node.TEXT_NODE ) {
      var text = dom_element.text;
      strings.push( text );
      texts.push( text );
    } else {
      // if an element for through all the children elements looking for text
      if( dom_element.type == Node.ELEMENT_NODE ) {
        // check to see if IMG or AREA element and to use ALT content if defined
        if((dom_element.tag_name == 'img') || (dom_element.tag_name == 'area')) {
     
          if (dom_element.alt) {
            strings.push(dom_element.alt);
            alts.push(dom_element.alt);
          }  
     
          if( dom_element.node.offsetHeight > o.height ) {
            o.height = dom_element.node.offsetHeight;
          } //endif
     
          if( dom_element.node.offsetWidth > o.width ) {
             o.width = dom_element.node.offsetWidth;
          } //endif
     
          o.image_count = o.image_count + 1;
     
        } else {
    
          for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
            if (!visible || dom_element.computed_style.is_visible_onscreen) {
              getText( dom_element.child_dom_elements[i], strings, texts, alts);
            }  
          } // endfor
     
        } // endif
      } // endif  
    } // endif
  } // end function getStrings

  // Create return object
  var o = {};
  var name_array = [];
  var name_from_text_nodes_array = [];
  var name_from_image_alt_array = [];
  o.height = 0;
  o.width = 0;
  o.image_count = 0;


  if (!visible || this.computed_style.is_visible_onscreen) {
    getText(this, name_array, name_from_text_nodes_array, name_from_image_alt_array); 
  }  
    
 
  o.name         = name_array.join("").normalizeSpace();
  o.name_from_text_nodes = name_from_text_nodes_array.join("").normalizeSpace().toLowerCase();
  o.name_from_image_alt = name_from_image_alt_array.join("").normalizeSpace().toLowerCase();
  return o;
 
}; // end function OpenAjax.cache.util.getAccessibleText


/**
 * @method getElementCount
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 * 
 * @desc Returns a String of the text content of a DOMElement and all its descendent DOMElements
 *
 * @return {Number}  Returns the number of descendent elements in a DOMElement object
 */
 
OpenAjax.a11y.cache.DOMElement.prototype.getElementCount = function() {

  function countElements(dom_element) {
    // If text node get the text and return
    if( dom_element.type == Node.ELEMENT_NODE ) {
      count++;
      for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
        countElements(dom_element.child_dom_elements[i]);
      } // end loop
    } 
  } // end function getStrings

 // Create return object
 var count = 0;

 countElements(this); 
 
 return count;
 
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.DOMElement
 *
 * @desc  Create a text String that represents the DOMElement object
 *
 * @return {String}
 */

OpenAjax.a11y.cache.DOMElement.prototype.toString = function() {
 var str = this.tag_name;
 
 if (this.role && this.role.length) return str + "[" + this.role + "]";
 if (this.id && this.id.length) return str + "#" + this.id;
 
 return str;
};

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
/* ---------------------------------------------------------------- */
/*                            DOMCache                              */
/* ---------------------------------------------------------------- */

/**
 * @constructor DOMCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructs a DOMCache Object 
 *          
 * @param  {String}  url       - URL of the page being evaluated
 * @param  {String}  title     - The value of title property of the document being evaluated
 * @param  {Object}  document  - The document object reference of the document being evaluated
 *
 * @property {String}  nls       - NLS cache items for properties
 * @property {String}  url       - URL of the page being evaluated
 * @property {String}  base_url  - Base URL of the page being evaluated calculated from the URL
 * @property {String}  title     - The value of title property of the document being evaluated
 * @property {Object}  document  - The document object reference of the document being evaluated
 *
 * DOM cache element objects
 * @property {Object}  element_cache          - dom element cache for all elements
 * @property {Object}  element_with_id_cache  - dom element cache items with a defined id
 *
 * Specialize cache element objects
 * @property {Object}  abbreviations_cache       - Cache of abbreviation elements 
 * @property {Object}  color_contrast_cache      - Cache of abbreviation items
 * @property {Object}  controls_cache            - Cache of form controls and widgets
 * @property {Object}  headings_landmarks_cache  - Cache of headings and abbreviations
 * @property {Object}  images_cache              - Cache of image and area elements
 * @property {Object}  keyboard_focus_cache      - Cache of all interactive elements on a web page
 * @property {Object}  languages_cache           - Cache of language change items
 * @property {Object}  links_cache               - Cache of a and area elements
 * @property {Object}  lists_cache               - Cache of list elements
 * @property {Object}  media_cache               - Cache of media elements
 * @property {Object}  tables_cache              - Cache of table elements
 *
 * @example
 *
 * var dom_cache = new OpenAjax.a11y.cache.DOMCache(url, title, doc, locale); 
 * dom_cache.updateDOMElementCache();
 * dom_cache.updateAllCaches();
 */

OpenAjax.a11y.cache.DOMCache = function (url, title, document) {

 this.nls = OpenAjax.a11y.cache_nls.getCacheNLS();
 
 this.url = url;
 this.base_url = this.url;

 var pos = this.base_url.lastIndexOf('/');
 if (pos >= 0) {
  this.base_url = this.base_url.substring(0,(pos+1));
 }
 else {
  this.base_url = this.base_url + "/";
 }

 this.title = title;
 this.document = document;

};
 
/**
 * @method initCache
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Initialize specialized caches
 *    The specialized caches will be updated all at once or or when
 *    needed by a rule depending on how an evaluation is requested
 */

OpenAjax.a11y.cache.DOMCache.prototype.initCache = function () {

 this.element_with_id_cache = new OpenAjax.a11y.cache.DOMElementCache();
 this.element_cache         = new OpenAjax.a11y.cache.DOMElementCache();

 this.abbreviations_cache      = new OpenAjax.a11y.cache.AbbreviationsCache(this);
// this.color_contrast_cache     = new OpenAjax.a11y.cache.ColorContrastCache(this);
 this.text_cache               = new OpenAjax.a11y.cache.TextCache(this);
 this.controls_cache           = new OpenAjax.a11y.cache.ControlsCache(this);
 this.headings_landmarks_cache = new OpenAjax.a11y.cache.HeadingsLandmarksCache(this);
 this.images_cache             = new OpenAjax.a11y.cache.ImagesCache(this);
 this.keyboard_focus_cache     = new OpenAjax.a11y.cache.KeyboardFocusCache(this);
 this.languages_cache          = new OpenAjax.a11y.cache.LanguagesCache(this);
 this.links_cache              = new OpenAjax.a11y.cache.LinksCache(this);
 this.lists_cache              = new OpenAjax.a11y.cache.ListsCache(this);
 this.media_cache              = new OpenAjax.a11y.cache.MediaCache(this);
 this.tables_cache             = new OpenAjax.a11y.cache.TablesCache(this);
 
 this.frame_count = 0;
 this.iframe_count = 0;

};

/**
 * @method isUpToDate
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Checks to see if the specified cache is up to date
 *
 * @param cache_name String Cache to update
 *
 * @return Object with two properties:
 *     o.up_to_date Boolean true if cache is up to date, otherwise false
 *     o.exists   Boolean true if cache exists, otherwise false
 */

OpenAjax.a11y.cache.DOMCache.prototype.isUpToDate = function (cache_name) {

 if (this[cache_name])
  return { up_to_date: this[cache_name].up_to_date, exists : true };
 else
  return { up_to_date: false, exists : false };

};

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Updates the specified cache
 *
 * @param cache_name String name of the cache to update
 *
 * @return {Boolean} Returns true if cache is updated, false if cache does not exist
 */

OpenAjax.a11y.cache.DOMCache.prototype.updateCache = function (cache_name) {

 if (this[cache_name]) {
  if (!this[cache_name].up_to_date) {
   this[cache_name].updateCache();
  }
  return true;
 }

 return false;

};

/**
 * @method traverseDOMElementsForAllCaches
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Updates all the specialized caches at one time, in general this
 *    is faster than updating the caches individually based on the
 *    needs of rules, but may create caches that will not be used if
 *    some rules are disabled
 *
 * @param  dom_element       Object  Current DOMElement object being processed
 * @param  landmark_info     Object  LandmarkInfo object containing current landmark and heading information for tree representations
 * @param  table_info        Object  TableInfo object containing current table information for tree representations
 * @param  control_info      Object  ControlInfo object containing current control information for tree representations
 * @param  list_info         Object  Current LanguageElement object that contains the DOMElement
 *
 * @return none
 */

OpenAjax.a11y.cache.DOMCache.prototype.traverseDOMElementsForAllCaches = function (dom_element,
                                          landmark_info,
                                          table_info,
                                          control_info,
                                          list_info,
                                          media_info) {

 if (!dom_element) return;
 // if an element for through all the children elements looking for text

 if (dom_element.type == Node.ELEMENT_NODE) {

  this.abbreviations_cache.updateCacheItems(dom_element);
  this.images_cache.updateCacheItems(dom_element);
  this.languages_cache.updateCacheItems(dom_element);
  this.links_cache.updateCacheItems(dom_element);
  
  var mi = this.media_cache.updateCacheItems(dom_element, media_info);
  var ci = this.controls_cache.updateCacheItems(dom_element, control_info);
  var hi = this.headings_landmarks_cache.updateCacheItems(dom_element, landmark_info);
  var li = this.lists_cache.updateCacheItems(dom_element, list_info);
  var ti = this.tables_cache.updateCacheItems(dom_element, table_info);

  var children_length = dom_element.child_dom_elements.length;
  for (var i = 0; i < children_length; i++ ) {
   this.traverseDOMElementsForAllCaches(dom_element.child_dom_elements[i], hi, ti, ci, li, mi);
  } // end loop
 } else {
   this.text_cache.updateCacheItems(dom_element);
   this.headings_landmarks_cache.updateCacheItems(dom_element, landmark_info);
 }
};


/**
 * @method updateAllCaches
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Traverses the DOMElements and 
 *       calls the update function to see which specialized caches want information on this element
 *
 * @return none
 */

OpenAjax.a11y.cache.DOMCache.prototype.updateAllCaches = function () {
 var i;
 var children = this.element_cache.child_dom_elements;
 var children_len = children.length;

 var hi = new OpenAjax.a11y.cache.LandmarkInfo(null);
 var ti = new OpenAjax.a11y.cache.TableInfo(null);
 var ci = new OpenAjax.a11y.cache.ControlInfo(null);
 var li = new OpenAjax.a11y.cache.ListInfo(null);
 var mi = new OpenAjax.a11y.cache.MediaInfo();
 

 for (i=0; i < children_len; i++) {
  this.traverseDOMElementsForAllCaches(children[i], hi, ti, ci, li, mi);
 }

 this.controls_cache.calculateControlLabels();
 this.controls_cache.applyAriaOwns();
 
 this.keyboard_focus_cache.createKeyboardFocusCache();

};

/**
 * @method updateDOMElementCache
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Updates a DOMElement object caches by traversing the DOM of the browser object
 *
 * @return DOMCache Object
 */

OpenAjax.a11y.cache.DOMCache.prototype.updateDOMElementCache = function () {

 var de;

 this.initCache();
 
 // add title information to DOMElement Cache
 
 this.addTitleDOMElement();

 // if the page contains a body element start there, since there are fewer elements to traverse
 if (this.document && this.document.body) {
  // OpenAjax.a11y.logger.debug("Creating DOM elements from body element");
  this.updateDOMElements(this.document.body, null, null);
 }
 // If there are frames start at the top element
 else {
  // OpenAjax.a11y.logger.debug("Creating DOM elements with frames");
  this.updateDOMElements(this.document, null, null);
 }

 // Calculate aria-descriptions
 this.calculateDescriptions();

 return this;

};

/**
 * @method addTitleDOMElement
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Adds a DOMElement to represent a TITLE
 *
 * @return Nothing
 */

OpenAjax.a11y.cache.DOMCache.prototype.addTitleDOMElement = function () {

  var n;
  var node;
  var de;
  var titles = this.document.getElementsByTagName('title');

  if (titles && titles.length && titles[0]) {
  
    node = titles[0];

    de = new OpenAjax.a11y.cache.DOMElement(node, null);
    
    this.document_has_title = true;

    de.addComputedStyle(null);
    de.calculateXPath(null);
  
    this.element_cache.addDOMElement(de);
    this.element_cache.addChild(de);
 
    // get any text nodes associated with the title element
    for (n = node.firstChild; n !== null; n = n.nextSibling) {
      this.updateDOMElements( n, de, null);
    } // end loop
    
  }
  else {
  
    node = this.document.createElement('title');
    
    de = new OpenAjax.a11y.cache.DOMElement(node, null);
    
    this.document_has_title = false;

    de.addComputedStyle(null);
    de.xpath = "";
  
    this.element_cache.addDOMElement(de);
    this.element_cache.addChild(de);

  }

};

/**
 * @method updateDOMElements
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Traverse document DOM and create a tree of DOMElement objects.
 *    The DOMElement objects will be used to generate more specific
 *    lists of elements without need to touch the document DOM.
 *    Additional information is collected on tables to be used in
 *    creating the table cache
 *
 * @param  {Object} node               - node is the current node object tbing analyzed
 * @param  {Object} parent_dom_element - DOMElement object that is the parent of the current node
 * @param  {Object} previous_sibling   - The DOMElement or DOMText object that is the previous sibling
 *
 * return nothing
 */

OpenAjax.a11y.cache.DOMCache.prototype.updateDOMElements = function (node, parent_dom_element, previous_sibling) {

  var n;
  var de;

  switch (node.nodeType ) {

  case Node.DOCUMENT_NODE:
  case Node.DOCUMENT_TYPE_NODE:
    // OpenAjax.a11y.logger.debug("Document node type");
    break;

  case Node.ELEMENT_NODE:
    // OpenAjax.a11y.logger.debug(node.tagName);
    
    var dom_element = new OpenAjax.a11y.cache.DOMElement(node, parent_dom_element);

    dom_element.addComputedStyle(parent_dom_element);
    
    dom_element.calculateXPath(parent_dom_element);
    this.element_cache.addDOMElement(dom_element);

    if (parent_dom_element) {
      parent_dom_element.has_element_children = true;
      parent_dom_element.addChild(dom_element);
    }
    else {
      this.element_cache.addChild(dom_element);
    }

    if (dom_element.id && dom_element.id.length) {
      // use append so that document_order of the dom_element does not get updated
      
      de = this.element_with_id_cache.getDOMElementById(dom_element.id);
      
      if (de) {
        dom_element.id_unique = OpenAjax.a11y.ID.NOT_UNIQUE;
        de.id_unique = OpenAjax.a11y.ID.NOT_UNIQUE;
      }
      
      this.element_with_id_cache.dom_elements.push(dom_element);
            
    }

    switch (dom_element.tag_name) {

    case 'frame':
    case 'iframe':

      if (dom_element.tag_name === 'frame') this.frame_count += 1;
      this.iframe_count += 1;

      var frame_doc = node.contentWindow.document;

//      OpenAjax.a11y.logger.debug("frame: " + node.src + " " + frame_doc);

      if (frame_doc && frame_doc.firstChild) {
        for (n = frame_doc.firstChild; n !== null; n = n.nextSibling) {
          this.updateDOMElements( n, dom_element);
        } // end loop
      }
      break;

    default:
      break;

    } // end switch
    
    var ps = null;

    for (n = node.firstChild; n !== null; n = n.nextSibling ) {
      ps = this.updateDOMElements(n, dom_element, ps);
    } // end loop
    
    return dom_element;
    break;

  case Node.TEXT_NODE:
   // OpenAjax.a11y.logger.debug("DOM node text: " + node.data);

   var dom_text = new OpenAjax.a11y.cache.DOMText(node, parent_dom_element);

   if (dom_text.text_length) {
   
     if (!previous_sibling || previous_sibling.type === Node.ELEMENT_NODE) {
   
       this.element_cache.addDOMText(dom_text);
       if (parent_dom_element) parent_dom_element.addChild(dom_text);
       return dom_text;
     
     } else {
   
       if (previous_sibling) previous_sibling.addText(dom_text.text);
       return previous_sibling;
     }  
   }
   else {
     return previous_sibling;
   }
   
   break;

  default:
    break;
  } // end switch

  return null;

};


/**
 * @method calculateDescriptions
 *
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Calculates a description if a element has an aria-describedby attribute defined
 */

OpenAjax.a11y.cache.DOMCache.prototype.calculateDescriptions = function () {

  var de;
  var dom_elements     = this.element_cache.dom_elements;
  var dom_elements_len = dom_elements.length;

  for (var i = 0; i < dom_elements_len; i++ ) {
    de = dom_elements[i];
    
    if (typeof de.aria_describedby === 'string' && de.aria_describedby.length && !de.calculated_aria_description) {
      de.calculated_aria_description = this.getTextFromIDs(de.aria_describedby);  
    }
  }
};

/**
 * @method getNameForLink
 *
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Calculates a computed accessible name for a link, using ARIA properties if defined
 *
 * @param {LinkElement} link - Link element object
 */

OpenAjax.a11y.cache.DOMCache.prototype.getNameForLink = function (link) {

  var SOURCE = OpenAjax.a11y.SOURCE;

  var computed_label = "";
  var computed_label_source = SOURCE.NONE;
  var de = link.dom_element;
  
  if (de.aria_labelledby) {
    computed_label = this.element_with_id_cache.getTextFromIds(de.aria_labelledby);
    computed_label_source = SOURCE.ARIA_LABELLEDBY;
  }
  else if (de.aria_label) {
    computed_label = de.aria_label;
    computed_label_source = SOURCE.ARIA_LABEL;
  }
  else {
    computed_label = de.getText();
    computed_label_source = SOURCE.TEXT_CONTENT;
  } 

  link.accessible_name                = computed_label;
  link.accessible_name_for_comparison = computed_label.toLowerCase().normalizeSpace();
  link.accessible_name_length         = computed_label.length;
  link.accessible_name_source         = computed_label_source;

  this.getDescriptionFromARIADescribedby(link);
};

/**
 * @method getNameFromARIALabel
 *
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Calculates a computed label and accessible name based on ARIA properties
 *
 * @param {Object} control - Control cache element object
 */

OpenAjax.a11y.cache.DOMCache.prototype.getNameFromARIALabel = function (control) {

  var SOURCE = OpenAjax.a11y.SOURCE;

  var computed_label = "";
  var computed_label_source = SOURCE.NONE;
  var de = control.dom_element;
  var wi = de.role_info;
  
  if (de.aria_labelledby) {
    computed_label = this.element_with_id_cache.getTextFromIds(de.aria_labelledby);
    computed_label_source = SOURCE.ARIA_LABELLEDBY;
  }
  else if (de.aria_label) {
    computed_label = de.aria_label;
    computed_label_source = SOURCE.ARIA_LABEL;
  }
  else if (wi && wi.nameFromContent) {
    computed_label = de.getText();
    computed_label_source = SOURCE.TEXT_CONTENT;
  } else if (de.title) {
    computed_label = de.title;
    computed_label_source = SOURCE.TITLE_ATTRIBUTE;
  }

  control.computed_label = computed_label;
  control.computed_label_length = computed_label.length;
  control.computed_label_source = computed_label_source;
  control.computed_label_for_comparison = computed_label.normalizeSpace().toLowerCase();
  control.accessible_name = computed_label;

  this.getDescriptionFromARIADescribedby(control);
};

/**
 * @method getDescriptionFromARIADescribedby
 *
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Calculates a description based on ARIA properties
 *
 * @param {Object} element - Cache element object
 */

OpenAjax.a11y.cache.DOMCache.prototype.getDescriptionFromARIADescribedby = function (element) {

  var de = element.dom_element;  
  if (typeof de !== 'object') de = element;
  
  if (de && de.aria_describedby) {
    element.accessible_description = this.element_with_id_cache.getTextFromIds(de.aria_describedby);
    element.accessible_description_for_comparison = element.accessible_description.toLowerCase().normalizeSpace();
  }
  else {
    element.accessible_description = "";  
    element.accessible_description_for_comparison = "";
  }
  
};


/**
 * @method getTextFromIDs
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Returns the text content of the elements identified in the list of ids
 *
 * @param {String}  ids  An string with space separated ids
 *
 * @return String
 */

OpenAjax.a11y.cache.DOMCache.prototype.getTextFromIDs = function (ids) {

  if (!ids || ids.length === 0) return ""; 
  
  return this.element_with_id_cache.getTextFromIds(ids);

};

/**
 * @method sortArrayOfObjects
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Sort an array of objects by one of its properties and marks any properties that are duplicates
 *
 * @param {Array}   objects   - Array of objects to sort
 * @param {String}  property  - Text string of property to sort
 * @param {Boolean} ascending - True sort by ascending values otherwise sort by descending values
 *
 * @return Array of sorted objects
 */

OpenAjax.a11y.cache.DOMCache.prototype.sortArrayOfObjects = function(objects, property, ascending ) {

  var swapped = false;
  var temp = null;
  var i;
  var return_objects = [];

  if( !objects && objects.length && !objects[0][property] ) {
    return return_objects;
  } // endif

  var objects_len = objects.length;
 
  for (i = 0; i < objects_len; i++) {
    return_objects[i] = objects[i];
    return_objects[i].duplicate = false;
  }  

  if( ascending ) {
    do{
      swapped = false;
      for (i = 1; i < objects_len; i++ ) {
        if (return_objects[i-1][property] > return_objects[i][property]) {
          // swap the values
          temp = return_objects[i-1];
          return_objects[i-1] = return_objects[i];
          return_objects[i] = temp;
          swapped = true;
        } 
        else {
          if (return_objects[i-1][property] === return_objects[i][property]) {
            return_objects[i-1].duplicate = true;
            return_objects[i].duplicate = true;
          }          
        }
      } // end loop
    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < objects_len; i++) {
        if (return_objects[i-1][property] < return_objects[i][property]) {
          // swap the values
          temp = return_objects[i-1];
          return_objects[i-1] = return_objects[i];
          return_objects[i] = temp;
          swapped = true;
        } 
        else {
          if (return_objects[i-1][property] === return_objects[i][property]) {
            return_objects[i-1].duplicate = true;
            return_objects[i].duplicate = true;
          }          
        }
      } // end loop
    } while (swapped);
  } 

  return return_objects;

};

/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*            OpenAjax Heading and Landmark Cache                   */ 
/* ---------------------------------------------------------------- */

/* ---------------------------------------------------------------- */
/*                    LandmarkInfo Object                           */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor LandmarkInfo
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc LandmarkInfo is the constructor for information related to landmarks
 *       in building the headings/landmark cache
 *
 * @param {LandmarkInfo}  landmark_info - Current landmark (if any)
 *
 * @property {Landmark/Heading Object}  landmark_element  - Parent landmark element 
 * @property {Landmark/Heading Object}  landmark_element  - Parent main landmark element 
 * @property {Landmark/Heading Object}  landmark_element  - Parent page object element 
 */

OpenAjax.a11y.cache.LandmarkInfo = function (landmark_info) {

  if (landmark_info) {
    this.landmark_element = landmark_info.landmark_element;
    this.main_element     = landmark_info.main_element;
    this.page_element     = landmark_info.page_element;
    this.body_element     = landmark_info.body_element;
    this.iframe_element   = landmark_info.iframe_element;
  }
  else {
    this.landmark_element = null;
    this.main_element     = null;
    this.page_element     = null;
    this.body_element     = null;
    this.iframe_element   = null;
  }
 
};

/* ---------------------------------------------------------------- */
/*                     HeadingsLandmarksCache                       */ 
/* ---------------------------------------------------------------- */

/**
 * @constructs HeadingsLandmarksCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc HeadingsLandmarksCache is the constructor for lists of heading and landmrk element objects and
 *       the root of a tree representation of the landmark and heading element relationships
 *
 * @param {DOMCache}  dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                          based on whether a rule needs the cache
 *
 * @property {Array}  child_cache_elements  - Root array of the tree representation of the landmarks and headings in the document 
 *
 * @property {Array}   landmark_elements        - List of all the landmark elements in the document 
 * @property {String}  landmarks_sort_property  - Name of the landmark element property the landmark elements array is currently sorted
 * @property {Number}  landmark_length          - The length of the landmark elements list, used in calculating cache id values 
 *
 * @property {Array}   heading_elements        - List of all the heading elements in the document 
 * @property {String}  headings_sort_property  - Name of the heading element property the heading elements array is currently sorted
 * @property {Number}  heading_length          - The length of the heading elements list, used in calculating cache id values 
 *
 * @property {Array}   elements_with_content   - List of content elements and text nodes outside of landmarks 
 *
 * @property {Array}   main_elements  - List of all the main landmark elements in the document 
 * @property {Number}  main_length    - The length of the main landmark elements list, used in calculating cache id values 
 *
 * @property {Array}   h1_elements    - List of all the h1 heading elements in the document 
 * @property {Number}  h1_length      - The length of the main landmark elements list, used in calculating cache id values 
 *
 * @property {Boolean}  has_main_landmarks  - True if document contians at lewast one main landmark, otherwise false
 * @property {Boolean}  has_title           - Title element is defined in the document 
 *
 * @property {TitleElement} title_element  - The title element is used as a placeholder for title rule results 
 * @property {PageElementHeadingsLandmarks}  page_element   - The body element is used as a placeholder rule results for items missing in a document like H1 elements and Main landmarks
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  
  this.up_to_date    = false;
 
  this.child_cache_elements   = [];
  
  this.landmark_elements = [];
  this.landmarks_sort_property = 'document_order';
  this.landmark_length  = 0;
  
  this.heading_elements = [];  
  this.headings_sort_property  = 'document_order';
  this.heading_length  = 0;
  
  this.elements_with_content = [];
  
  this.main_elements = [];
  this.main_length   = 0;

  this.h1_elements   = [];
  this.h1_length     = 0;
  
  this.has_h1_elements    = false;
  this.has_main_landmarks = false;
  this.has_title          = false;

  this.title_element = null;  
  this.page_element  = null;  

  this.iframe_elements = [];

};

/**
 * @method addChildElement
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc Adds a landmark or header element object to the root level of a tree of landmark/heading elements  
 *
 * @param {LandmarkElement | HeadingElement} child_element - Landmark or heading element object to add
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.addChildElement = function (child_element) {

  // item must exist and have the position property
  if (child_element) {
    this.child_cache_elements.push(child_element);
  } 

};

/**
 * @method addLandmarkElement
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc   Adds a landmark element object to the heading elements list
 *
 * @param  {LandmarkElement} heading_element  - Landmark element object to a landmark elements list
 *
 * @return {Number} Returns the length of the landmark elements list
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.addLandmarkElement = function (landmark_element) {

  if (landmark_element) {
    this.landmark_length = this.landmark_length + 1;
    landmark_element.document_order = this.landmark_length;
    landmark_element.cache_id = "landmark_" + this.landmark_length;
    this.landmark_elements.push(landmark_element);
  } 
  
  return this.landmark_length;
};

/**
 * @method addHeadingElement
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc   Adds a heading element object to the heading elements list
 *
 * @param  {HeadingElement} heading_element  - HeadingElement object to a heading_elements array
 *
 * @return {Number} Returns the length of the heading elements list
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.addHeadingElement = function (heading_element) {

  if (heading_element) {
    this.heading_length = this.heading_length + 1;
    heading_element.document_order = this.heading_length;
    heading_element.cache_id = "heading_" + this.heading_length;
    this.heading_elements.push(heading_element);
  } 
  return this.heading_length;
};

/**
 * @method addChildMainElement
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc Adds a main landmark or h1 heading element object to the root level of a tree of title and main elements  
 *
 * @param {MainElement | H1Element}  child_element - Main landmark or h1 heading element object to add
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.addChildMainElement = function (child_element) {

  // item must exist and have the position property
  if (child_element) {
    this.child_cache_elements.push(child_element);
  } 

};

/**
 * @method addH1Element
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc   Adds a h1 element object to the h1 heading elements list
 *
 * @param  {H1Element}  h1_element  -  h1 heading element to add 
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.addH1Element = function (h1_element) {

  if (h1_element && h1_element.main_type === OpenAjax.a11y.MAIN.H1_ELEMENT) {
    this.h1_length = this.h1_length + 1;
    h1_element.document_order = this.h1_length;
    h1_element.cache_id = "h1_" + this.h1_length;
    this.h1_elements.push(h1_element);
  } 

};

/**
 * @method addMainElement
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc    Adds a main, h1 or title element object to the main_elements array and cacluates a cache id value
 *
 * @param  {MainElement | H1Element | TitleElement | PageElementHeadingsLandmarks}  main_element  Main, h1 heading or title element object to add 
 *
 * @return  {Number}  length is the number of elements in the main_elements list
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.addMainElement = function (main_element) {

  if (main_element) {
    this.main_length = this.main_length + 1;
    main_element.document_order = this.main_length;
    main_element.cache_id = "main_" + this.main_length;
    this.main_elements.push(main_element);
  } 
  
  return this.length;
};


/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc  Finds the landmark or heading element object with the cache id value 
 *
 * @param  {String}  cache_id  - cache id of a landmark element object
 *
 * @return  {LandmarkElement | HeadingElement | null} Returns a landmark element object if cache id found, otherwise null
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.getItemByCacheId = function (cache_id) {

  var i;
  var elements_with_content     = this.elements_with_content;
  var elements_with_content_len = elements_with_content.length;
  var item = null;

  item = this.getLandmarkElementByCacheId(cache_id);
  if (item) return item;
  
  item = this.getHeadingElementByCacheId(cache_id);
  if (item) return item;

  for (i = 0; i < elements_with_content_len; i++) {
    item = this.elements_with_content[i];
    if (item.cache_id == cache_id) return item;
  }

  return item;

};

/**
 * @method getLandmarkElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc  Finds the landmark element object with the cache id value 
 *
 * @param  {String}  cache_id  - cache id of a landmark element object
 *
 * @return  {LandmarkElement | null} Returns a landmark element object if cache id found, otherwise null
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.getLandmarkElementByCacheId = function (cache_id) {
 
  var i; 
  var landmark_elements_len = this.landmark_elements.length;

  if (cache_id) {
    for (i=0; i<landmark_elements_len; i++) {
      if (this.landmark_elements[i].cache_id == cache_id) {
        return this.landmark_elements[i];
      } 
    } // end loop
  }
 return null;
};

/**
 * @method getHeadingElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc  Finds the heading element object with the cache id value 
 *
 * @param  {String}  cache_id  - cache id of a heading element object
 *
 * @return  {HeadingElement | null}  Returns a heading object if cache id found, otherwise null
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.getHeadingElementByCacheId = function (cache_id) {
 
  var i;
  var heading_elements_len = this.heading_elements.length;

  if (cache_id) {
    for (i=0; i<heading_elements_len; i++) {
      if (this.heading_elements[i].cache_id == cache_id) {
        return this.heading_elements[i];
      } 
    } // end loop
  }
 
  return null;
};

/**
 * @method initCache
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc Empties the landmark and headings cache 
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.initCache = function () {

  this.child_cache_elements  = [];
  
  this.up_to_date = false;
 
  this.landmark_elements = [];
  this.landmark_length = 0;
  this.landmarks_sort_property = 'document_order';
  
  this.heading_elements = [];
  this.heading_length = 0;
  this.headings_sort_property = 'document_order';
 
  this.main_elements = [];
  this.main_length   = 0;
  
  this.h1_elements   = [];
  this.h1_length     = 0;
  
  this.page_element = null;
  
  this.has_h1_elements    = false;
  this.has_main_landmarks = false;
  this.has_title          = false;
  
  this.iframe_elements = [];

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc Updates the landmarks and headings cache by checking to see if a DOMElement
 *          should be added
 *  
 * @param  {DOMElement}    dom_element    - DOMElement object to check for inclusion in links cache
 * @param  {LandmarkInfo}  landmark_info  - Information about the current landmarks that are parents to this item
 * 
 * @return {LandmarkInfo}  Returns updated landmark info object
 */
 
OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.updateCacheItems = function (dom_element, landmark_info) {

  var me;
  var le;
  var he;
  var li = new OpenAjax.a11y.cache.LandmarkInfo(landmark_info);
  var tag_name = dom_element.tag_name;
  
  dom_element.parent_landmark = landmark_info.landmark_element;
  
  if (dom_element.type == Node.ELEMENT_NODE) {

    if (dom_element.is_landmark) {
    
      if (dom_element.role == 'main') {
   
        this.has_main_landmarks = true;
 
        me = new OpenAjax.a11y.cache.MainElement(dom_element, dom_element, landmark_info.landmark_element);    

        this.dom_cache.getNameFromARIALabel(me);

        this.addLandmarkElement(me);
        this.addMainElement(me);  

        if (landmark_info.landmark_element) {
          landmark_info.landmark_element.addChildElement(me);
        } 
        else {
          this.addChildElement(me);  
        }

        li.landmark_element = me;
        li.main_element     = me;
    
        return li;
      }
      else {
   
        le = new OpenAjax.a11y.cache.LandmarkElement(dom_element, landmark_info.landmark_element);    

        this.dom_cache.getNameFromARIALabel(le);

        this.addLandmarkElement(le);

        if (landmark_info.landmark_element) {
          landmark_info.landmark_element.addChildElement(le);
        } 
        else {
          this.addChildElement(le);  
        }
  
        li.landmark_element = le;

        return li;
      }  
    }
    
    if (tag_name == 'h1') {
  
      this.has_h1_elements = true;

      he = new OpenAjax.a11y.cache.H1Element(dom_element, landmark_info.landmark_element, landmark_info.main_element);    

      this.addH1Element(he);
      this.addHeadingElement(he);

      if (landmark_info.main_element) { 
        landmark_info.main_element.addH1Element(he);
      }  

      if (landmark_info.landmark_element) {
        landmark_info.landmark_element.addChildElement(he);
      } 
      else {
        this.addChildElement(he);  
      }

      he.isH1UsedAsLabelForMainRole();

      return li;
    }

    if ((tag_name == 'h2') || 
        (tag_name == 'h3') || 
        (tag_name == 'h4') || 
        (tag_name == 'h5') || 
        (tag_name == 'h6')) {
   
      he = new OpenAjax.a11y.cache.HeadingElement(dom_element, landmark_info.landmark_element);    
  
      this.addHeadingElement(he);

      if (landmark_info.landmark_element) {
        landmark_info.landmark_element.addChildElement(he);
      } 
      else {
        this.addChildElement(he);  
      }

      return li;
    }
    
    if (tag_name == 'title' && !this.has_title) {
  
      me = new OpenAjax.a11y.cache.TitleElement(dom_element);    
   
      // There is only one title for a document, even when there are frames and iframes
      this.has_title = true;

      this.title_element = me;
    
      return li;
    }

    if (tag_name == 'body' && !this.page_element) {
  
      me = new OpenAjax.a11y.cache.PageElementHeadingsLandmarks(dom_element, li.main_element);    
   
      // There is only one body element for a document, even when there are frames and iframes
      this.page_element = me;
    
      return li;
    }
    
    
    
    // elements that contain rendered content without having child dom text nodes
    if ((tag_name == 'area')     ||
        (tag_name == 'canvas')   ||
        (tag_name == 'input')    ||
        (tag_name == 'img')      ||
        (tag_name == 'textarea') ||
        (tag_name == 'select')) {
        
      this.elements_with_content.push(dom_element);
      
      if (dom_element.parent_landmark) dom_element.parent_landmark.addToElementCount(1); 
      
      return li;
    }  

    // elements that may have rendered content without having child dom text nodes
    if ((tag_name == 'applet')   ||
        (tag_name == 'embed')    ||
        (tag_name == 'object')) {
      dom_element.may_have_renderable_content = true;  
      this.elements_with_content.push(dom_element);
      
//      OpenAjax.a11y.logger.debug("Parent Element: " +  dom_element.parent_landmark + " (" + dom_element + ")");

      if (dom_element.parent_landmark) dom_element.parent_landmark.addToElementCount(1); 
      
      return li;
    }  

    // Keep track of iframe elements
    if ((tag_name == 'iframe')) {
      this.iframe_elements.push(dom_element);
      li.iframe_element = dom_element;
      return li;
    }  

    // Keep track of body elements
    if ((tag_name == 'body')) {
      li.body_element = dom_element;
      return li;
    }  


  }
  else {
    tag_name = dom_element.parent_element.tag_name;
    if (tag_name != 'title'  && 
        tag_name != 'script' && 
        tag_name != 'style'  &&
        dom_element.text_length) {
      this.elements_with_content.push(dom_element);

//      OpenAjax.a11y.logger.debug("Parent Element: " +  dom_element.parent_landmark + " (" + dom_element + ")");

      if (dom_element.parent_landmark) dom_element.parent_landmark.addToElementCount(1); 

    }  
  }
  
  return li;
  
};

/**
 * @method traverseDOMElementsForLandmarkElements
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc Traverses DOMElement objects in the tree to update the landmarks and headings cache 
 *
 * @param  {DOMElement}  dom_element - DOMElement object to check for inclusion in landmarks and headings cache
 */
 
OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.traverseDOMElementsForLandmarkElements = function (dom_element, landmark_info) {

  if (!dom_element) return;

  if (dom_element.type == Node.ELEMENT_NODE) {

    var li = this.updateCacheItems(dom_element, landmark_info);
    
    for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
      this.traverseDOMElementsForLandmarkElements(dom_element.child_dom_elements[i], li);
    } 
  }
};


/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc Traverses the DOMElements to update the landmarks and heading cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the landmark and headings cache are disabled, this 
 *       cache would not be updated
 */
 
OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.updateCache = function () {
  var i;
  var li;
  var children = this.dom_cache.element_cache.child_dom_elements;
  var children_len = children.length;
 
  this.initCache();
 
  li = new OpenAjax.a11y.cache.LandmarkInfo(null);
 
  for (i=0; i < children_len; i++) {
    this.traverseDOMElementsForLandmarkElements(children[i], li);
  }  

  this.up_to_date = true;
};


/**
 * @method sortLandmarkElements
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc    Sorts the landmark elements list based on a property of the landmark element object
 *
 * @param   {String}   property   - LandmarkElement object property used to sort the cache
 * @param   {Boolean}  ascending  - true if sort in ascending order; false in descending order
 *
 * @return  {Boolean}  Return true if list was sorted; false was not sorted due to an error
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.sortLandmarkElements = function(property, ascending ) {

  var swapped = false;
  var temp = null;
  var i;

  if( this.landmark_elements && this.landmark_elements.length && !this.landmark_elements[0][property] ) {
    return false;
  } // endif

  var landmark_elements_len = this.landmark_elements.length;

  if( ascending ) {
    do{
      swapped = false;
      for (i = 1; i < landmark_elements_len; i++ ) {
        if (this.landmark_elements[i-1][property] > this.landmark_elements[i][property]) {
          // swap the values
          temp = this.landmark_elements[i-1];
          this.landmark_elements[i-1] = this.landmark_elements[i];
          this.landmark_elements[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < landmark_elements_len; i++) {
        if (this.landmark_elements[i-1][property] < this.landmark_elements[i][property]) {
          // swap the values
          temp = this.landmark_elements[i-1];
          this.landmark_elements[i-1] = this.landmark_elements[i];
          this.landmark_elements[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  } 

  this.landmark_sort_property = property;

  return true;

};

/**
 * @method sortHeadingElements
 *
 * @memberOf OpenAjax.a11y.cache.HeadingsLandmarksCache
 *
 * @desc  Sorts the heading_elements array based on a property of the HeadingElement object
 *
 * @param {String}   property   - HeadingElement object property used to sort the cache
 * @param {Boolean}  ascending  -  true if sort in ascending order; false in descending order
 *
 * @return  {Boolean}  true if list was sorted, false if not
 */

OpenAjax.a11y.cache.HeadingsLandmarksCache.prototype.sortHeadingElements = function(property, ascending ) {

  var swapped = false;
  var temp = null;
  var i;

  if( this.heading_elements && this.heading_elements.length && !this.heading_elements[0][property] ) {
    return false;
  } // endif

  var heading_elements_len = this.heading_elements.length;

  if( ascending ) {
    do{
      swapped = false;
      for (i = 1; i < heading_elements_len; i++ ) {
        if (this.heading_elements[i-1][property] > this.heading_elements[i][property]) {
          // swap the values
          temp = this.heading_elements[i-1];
          this.heading_elements[i-1] = this.heading_elements[i];
          this.heading_elements[i] = temp;
          swapped = true;
        } 
      } // end loop

    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < heading_elements_len; i++) {
   
        if (this.heading_elements[i-1][property] < this.heading_elements[i][property]) {
          // swap the values
          temp = this.heading_elements[i-1];
          this.heading_elements[i-1] = this.heading_elements[i];
          this.heading_elements[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  } 

  this.heading_sort_property = property;

  return true;

};

/* ---------------------------------------------------------------- */
/*                       LandmarkElement                            */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor LandmarkElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a landmark element object used to hold information about a landmark 
 *
 * @param  {DOMelement}       dom_element      - The dom element object representing the landmark element 
 * @param  {LandmarkElement}  parent_landmark  - Information about the parent landmark (NOTE: can be null)
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the landmark element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the landmark element in the document in relationship to other landmark elements
 * @property  {String}      role            - String representing the type of landmark
 *
 * @property  {Array}  child_cache_elements  - Array of child cache landmark and heading element objects as part of cache landmark and header tree 
 *
 * @property  {String}   label                  - Accessible label of the landmark 
 * @property  {Number}   label_length           - Length of label text 
 * @property  {Number}   label_source           - Constant representing the source of the label (i.e. aria-label, aria-labelledby, title...) 
 * @property  {String}   label_for_comparison   - Accessible label for comparison (i.e. lowercase, trimmed and space normalized)
 */

OpenAjax.a11y.cache.LandmarkElement = function (dom_element, parent_landmark) {

  this.dom_element           = dom_element;
  this.cache_id              = "";
  this.document_order        = 0;
  this.role                  = dom_element.role;

  this.child_cache_elements  = [];

  this.parent_landmark       = parent_landmark;
 
  this.computed_label                 = "";
  this.computed_label_length          = 0;
  this.computed_label_source          = OpenAjax.a11y.SOURCE.NONE;
  this.computed_label_for_comparison  = "";
  this.accessible_name                = "";
  
  this.elements_with_content_count = 0;

};

/**
 * @method addChildElement
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Adds a LandmarkElement or HeaderElement object to the tree of landmark/heading elements  
 *
 * @param {LandmarkElement | HeadingElement}  child_element  - Landmark element or heading element object to add 
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.addChildElement = function (child_element) {

  if (child_element) {
    this.child_cache_elements.push(child_element); 
  }  

}; 


/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method addToElementCount
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Adds N elements to the count of elements with content
 *
 * @param {Number}  n  - Number of elements to add to count
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.addToElementCount = function (n) {

  if (n > 0) this.elements_with_content_count += n;

};


 /**
 * @method getElementsWithContentCount
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Get the number of child elements with content, inlcuding the elements with 
 *       content of child landmark elements 
 *
 * @return {Number}  Number of elements with content 
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getElementsWithContentCount = function () {
    
  var count = this.elements_with_content_count;
    
  var child_elements     = this.child_cache_elements;
  var child_elements_len = child_elements.length;
      
  for (var i = 0; i < child_elements_len; i++) {
      
    var cle = child_elements[i];
      
    if (typeof cle.getElementsWithContentCount === 'object') {
      count += cle.getElementsWithContentCount();
    }  
  }
    
  return count;
};

 /**
 * @method getHeadings
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Get all the heading elements in a landmark
 *
 * @return {Array}  Array of heading elements 
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getHeadings = function () {
    
  var headings = [];
    
  var child_elements     = this.child_cache_elements;
  var child_elements_len = child_elements.length;
      
  for (var i = 0; i < child_elements_len; i++) {
      
    var cle = child_elements[i];
      
    if (typeof cle.level === 'number') headings.push(cle);
    else if (typeof cle.getHeadings === 'object') headings = headings.concat(cle.getHeadings());
  }
    
  return headings;
};


/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'computed_label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_for_comparison');
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_name');
  cache_nls.addPropertyIfDefined(properties, this, 'parent_landmark');
  cache_nls.addPropertyIfDefined(properties, this, 'elements_with_content_count');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.LandmarkElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.LandmarkElement
 *
 * @desc Returns a text string representation of the landmark element 
 *
 * @return {String} Returns string represention the landmark element object
 */
 
OpenAjax.a11y.cache.LandmarkElement.prototype.toString = function () {
 var de = this.dom_element;
 if (this.accessible_name && this.accessible_name.length) return de.tag_name + "[" + de.role + "]: " + this.accessible_name;  
 return de.tag_name + "[" + de.role + "]";   
}; 

/* ---------------------------------------------------------------- */
/*                       HeadingElement                             */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor HeadingElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a heading element object used to hold information about a h1 - h6 heading elements 
 *
 * @param  {DOMelement}       dom_element      - The dom element object representing the heading element 
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the optgroup element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the heading element in the document in relationship to other heading elements
 *
 * @property  {Object}  parent_landmark     - Cache item object that is the parent landmark element (note: can be null)
 * @property  {Number}  level               - Level of the heading
 *
 * @property  {String}   name                  - Calculated accessible name of the heading 
 * @property  {Number}   name_length           - Length of accessible name 
 * @property  {String}   name_for_comparison   - Accessible name for comparison (i.e. lowercase, trimmed and space normalized)
 * @property  {String}   name_from_text_nodes  - Accessible name content from text nodes
 * @property  {String}   name_from_image_alt   - Accessible name content from alt content of images
 * @property  {Number}   image_count           - Number of images that are descendents of the link
 * @property  {Boolean}  text_only_from_image  - true if accessble name is onky from an image, otherwise false 
 */

OpenAjax.a11y.cache.HeadingElement = function (dom_element, parent_landmark) {
 
  this.dom_element     = dom_element;
  this.cache_id        = "";
  this.document_order  = 0;
  
  this.parent_landmark = parent_landmark;
   
  switch( dom_element.tag_name ) {
  case 'h1':
    this.level = 1;
    break;
    
  case 'h2':
    this.level = 2;
    break;
    
  case 'h3':
    this.level = 3;
    break;
    
  case 'h4':
    this.level = 4;
    break;
    
  case 'h5':
    this.level = 5;
    break;
    
  case 'h6':
    this.level = 6;
    break;
    
  default:
    this.level = 0;   
    break;
  } // end switch

  var ano = dom_element.getTextObject(true);  // text content must be visible
  
  this.name                  = ano.name;
  this.name_length           = ano.name.length;
  this.name_for_comparison   = ano.name.normalizeSpace().toLowerCase();
  this.name_from_text_nodes  = ano.name_from_text_nodes;
  this.name_from_image_alt   = ano.name_from_image_alt;
  this.image_count           = ano.image_count;
  this.text_only_from_image  = (ano.name_from_text_nodes.length === 0) && (ano.name_from_image_alt.length > 0);
  
  return this;
    
};


/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.HeadingElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.HeadingElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.HeadingElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.HeadingElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

 cache_nls.addPropertyIfDefined(properties, this, 'name');
 cache_nls.addPropertyIfDefined(properties, this, 'name_for_comparison');
 cache_nls.addPropertyIfDefined(properties, this, 'name_from_text_nodes');
 cache_nls.addPropertyIfDefined(properties, this, 'name_from_image_alt');
 cache_nls.addPropertyIfDefined(properties, this, 'image_count');
 cache_nls.addPropertyIfDefined(properties, this, 'text_only_from_image');
 cache_nls.addPropertyIfDefined(properties, this, 'parent_landmark');
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.HeadingElement.prototype.getCachePropertyValue = function (property) {

//  OpenAjax.a11y.logger.debug("Heading property: " + property + " value= " + this[property]);

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
  
};

/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.HeadingElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.HeadingElement
 *
 * @desc Returns a text string representation of the heading (h1-h6) element 
 *
 * @return {String} Returns string represention the heading element object
 */
  
OpenAjax.a11y.cache.HeadingElement.prototype.toString = function() {
  if (this.name && this.name_length) {
    return "h" + this.level + ": " + this.name;
  }
  else {
    return "h" + this.level + ": no content";
  }

};

/* ---------------------------------------------------------------- */
/*                        MainElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor MainElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a main landmark element object used to hold information about a main landmark 
 *
 * @param  {DOMElement}   dom_element      - The dom element object representing the landmark element 
 * @param  {MainElement}  parent_landmark  - Information about the parent landmark (NOTE: can be null)
 *
 * @property  {DOMElement}   dom_element      - Reference to the dom element representing the main landmark element
 * @property  {String}       cache_id         - String that uniquely identifies the cache element object in the cache
 * @property  {Number}       document_order   - Ordinal position of the title and main cache items in the document to other title and main cache items
 * @property  {String}       role             - String identifying the landmark as "main"
 *
 * @property  {MainElement}  parent_landmark  - Information about the parent main landmark (NOTE: can be null)
 *
 * @property  {Array}  child_cache_elements  - List of child cache title element, main landmarks and h1 heading element objects as part of cache title and main elements tree 
 *
 * @property  {Array}   h1_elements  -  List of all the h1 heading elements that are children of the main landmark
 * @property  {Number}  type         -  Constant representing the type of main landmark
 *
 * @property  {String}   label                  - Accessible label of the landmark 
 * @property  {Number}   label_length           - Length of label text 
 * @property  {Number}   label_source           - Constant representing the source of the label (i.e. aria-label, aria-labelledby, title...) 
 * @property  {String}   label_for_comparison   - Accessible label for comparison (i.e. lowercase, trimmed and space normalized)
 */

OpenAjax.a11y.cache.MainElement = function (dom_element, parent_landmark) {

  this.dom_element     = dom_element;
  this.cache_id        = "";  
  this.document_order  = 0;
  this.role            = "main";

  this.child_cache_elements = [];
  this.h1_elements          = [];
  this.main_type            = OpenAjax.a11y.MAIN.ROLE_MAIN;
  
  this.parent_landmark = parent_landmark; // restricted to main landmarks
 
  this.computed_label                 = "";
  this.computed_label_length          = 0;
  this.computed_label_source          = OpenAjax.a11y.SOURCE.NONE;
  this.computed_label_for_comparison  = "";
  this.accessible_name                = "";
  
  this.elements_with_content_count = 0;

}; 


/**
 * @method addChildElement
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Adds a child landmark or heading object to the tree of landmarks and heading elements  
 *
 * @param {Object}  cache_element  -  landmark or heading element object to add to the tree
 */

OpenAjax.a11y.cache.MainElement.prototype.addChildElement = function (cache_element) {

  if (cache_element) {
    this.child_cache_elements.push(cache_element); 
  }  

}; 

/**
 * @method addH1Element
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Adds a H1 element to the list of H1 elements that are a child elements of the main content   
 *
 * @param {H1Element}  h1_element  -  H1 element object to add to list 
 */

OpenAjax.a11y.cache.MainElement.prototype.addH1Element = function (h1_element) {

  if (h1_element) {
    this.h1_elements.push(h1_element); 
  }  

}; 


/**
 * @method addToElementCount
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Adds N elements to the count of elements with content
 *
 * @param {Number}  n  - Number of elements to add to count
 */

OpenAjax.a11y.cache.MainElement.prototype.addToElementCount = function (n) {

  if (n > 0) this.elements_with_content_count += n;

};

 /**
 * @method getElementsWithContentCount
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Get the number of child elements with content, inlcuding the elements with 
 *       content of child landmark elements 
 *
 * @return {Number}  Number of elements with content 
 */

OpenAjax.a11y.cache.MainElement.prototype.getElementsWithContentCount = function () {
    
  var count = this.elements_with_content_count;
    
  var child_elements     = this.child_cache_elements;
  var child_elements_len = child_elements.length;
      
  for (var i = 0; i < child_elements_len; i++) {
      
    var cle = child_elements[i];
      
    if (typeof cle.getElementsWithContentCount === 'object') {
      count += cle.getElementsWithContentCount();
    }  
  }
    
  return count;
};

 /**
 * @method getHeadings
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Get all the heading elements in a landmark
 *
 * @return {Array}  Array of heading elements 
 */

OpenAjax.a11y.cache.MainElement.prototype.getHeadings = function () {
    
  var headings = [];
    
  var child_elements     = this.child_cache_elements;
  var child_elements_len = child_elements.length;
      
  for (var i = 0; i < child_elements_len; i++) {
      
    var cle = child_elements[i];
      
    if (typeof cle.level === 'number') headings.push(cle);
    else if (typeof cle.getHeadings === 'object') headings = headings.concat(cle.getHeadings());
  }
    
  return headings;
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.MainElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.MainElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.MainElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.MainElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'computed_label');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_source');
  cache_nls.addPropertyIfDefined(properties, this, 'computed_label_for_comparison');
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_name');
  cache_nls.addPropertyIfDefined(properties, this, 'parent_landmark');
  cache_nls.addPropertyIfDefined(properties, this, 'elements_with_content_count');
  
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.MainElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.MainElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.MainElement
 *
 * @desc Returns a text string representation of the main landmark element 
 *
 * @return {String} Returns string represention the landmark element object
 */
  
OpenAjax.a11y.cache.MainElement.prototype.toString = function () {
  if (this.accessible_name && this.accessible_name.length) return this.dom_element.tag_name + "[main]: " + this.accessible_name;  
  return this.dom_element.tag_name + "[main]";  
};

/* ---------------------------------------------------------------- */
/*                         H1Element                                */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor H1Element
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a h1 heading element object used to hold information about a h1 heading elements used for titling 
 *
 * @param  {DOMelement}       dom_element      - The dom element object representing the heading element 
 * @param  {LandmarkElement}  parent_landmark  - Information about the parent landmark (NOTE: can be null)
 * @param  {MainElement}      main_landmark    - Information about the parent main landmark (NOTE: can be null)
 *
 * @property  {DOMElement}   dom_element      - Reference to the dom element representing the optgroup element
 * @property  {String}       cache_id         - String that uniquely identifies the cache element object in the cache
 * @property  {Number}       document_order   - Ordinal position of the title and main cache items in the document to other title and main cache items
 *
 * @property  {LandmarkElement}  parent_landmark  - Information about the parent landmark (NOTE: can be null)
 * @property  {MainElement}      main_landmark    - Information about the parent main landmark (NOTE: can be null)
 *
 * @property  {Array}  child_cache_elements  - List of child cache title element, main landmarks and h1 heading element objects as part of cache title and main elements tree  
 *
 * @property  {Number}   type               -  Constant representing the type of main landmark
 * @property  {Boolean}  is_label_for_main  - true if h1 is being used as a label for main landmark, otherwise false
 * @property  {Boolean}  is_child_of_main   - true if h1 is the child of the main landmark it is a label for, otherwise false
 *
 * @property  {String}   name                  - Calculated accessible name of the heading 
 * @property  {Number}   name_length           - Length of accessible name 
 * @property  {String}   name_for_comparison   - Accessible name for comparison (i.e. lowercase, trimmed and space normalized)
 */

OpenAjax.a11y.cache.H1Element = function (dom_element, parent_landmark, main_landmark) {

  this.dom_element     = dom_element;
  this.cache_id        = "";  
  this.document_order  = 0;
  
  this.parent_landmark  = parent_landmark; // restricted to main landmarks
  this.main_landmark    = main_landmark;   // restricted to main landmarks
  this.child_cache_elements = [];   // The child array is always empty for an H1Element
  this.level = 1;

  
  this.main_type            = OpenAjax.a11y.MAIN.H1_ELEMENT;
  this.is_label_for_main    = false;
  
  if (main_landmark) this.is_child_of_main = true;
  else this.is_child_of_main     = false;

  this.name                 = dom_element.getText();
  this.name_length          = this.name.length;
  this.name_for_comparison  = this.name.normalizeSpace().toLowerCase();
  
}; 

/**
 * @method isH1UsedAsLabelForMainRole
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 * 
 * @desc  Determines if an H1 element is being used as a label for a main Role
 *
 * @return  {Boolean}  True if the h1 element is being used as a label for the main landmark it is contained in, otherwise false
 */

OpenAjax.a11y.cache.H1Element.prototype.isH1UsedAsLabelForMainRole = function () {

  if (this.dom_element.id.length === 0 ||
      this.main_landmark === null) {
    this.is_label_for_main = false;  
    return;
  }  

  var me = this.main_landmark;
  var de = me.dom_element;

  if (de.aria_labelledby && de.aria_labelledby.indexOf(this.dom_element.id) >= 0) {
    this.is_label_for_main = true;   
  }
      
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.H1Element.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.H1Element.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.H1Element.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.H1Element.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

 cache_nls.addPropertyIfDefined(properties, this, 'main_type');
 cache_nls.addPropertyIfDefined(properties, this, 'name');
 cache_nls.addPropertyIfDefined(properties, this, 'name_for_comparison');
 cache_nls.addPropertyIfDefined(properties, this, 'name_from_text_nodes');
 cache_nls.addPropertyIfDefined(properties, this, 'name_from_image_alt');
 cache_nls.addPropertyIfDefined(properties, this, 'image_count');
 cache_nls.addPropertyIfDefined(properties, this, 'text_only_from_image');
 cache_nls.addPropertyIfDefined(properties, this, 'is_label_for_main');
 cache_nls.addPropertyIfDefined(properties, this, 'is_child_of_main');
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.H1Element.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.H1Element.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.H1Element
 *
 * @desc Returns a text string representation of the h1 heading element 
 *
 * @return {String} Returns string represention the h1 heading element object
 */
  
OpenAjax.a11y.cache.H1Element.prototype.toString = function () {
  if (this.name && this.name.length) return "h1: " + this.name; 
  else return "h1: no content";
};

/* ---------------------------------------------------------------- */
/*                       TitleElement                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor TitleElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a title element object used to hold information about a title element
 *
 * @param  {DOMelement}   dom_element      - The dom element object representing the heading element 
 * @param  {MainElement}  parent_landmark  - Information about the parent landmark (NOTE: can be null)
 *
 * @property  {DOMElement}   dom_element      - Reference to the dom element representing the optgroup element
 * @property  {String}       cache_id         - String that uniquely identifies the cache element object in the cache
 * @property  {Number}       document_order   - Ordinal position of the title and main cache items in the document to other title and main cache items
 *
 * @property  {Number}   type  -  Constant representing the title element 
 *
 * @property  {String}   name                  - Calculated accessible name of the heading 
 * @property  {Number}   name_length           - Length of accessible name 
 * @property  {String}   name_for_comparison   - Accessible name for comparison (i.e. lowercase, trimmed and space normalized)
 */

OpenAjax.a11y.cache.TitleElement = function (dom_element) {

  this.dom_element     = dom_element;
  this.cache_id        = "title";
  this.document_order  = 0;

  this.main_type          = OpenAjax.a11y.MAIN.TITLE_ELEMENT;

  this.name                 = dom_element.getText();
  this.name_length          = this.name.length;
  this.name_for_comparison  = this.name.normalizeSpace().toLowerCase();

  // these can probably be removed some day
  this.parent_landmark      = null; // restricted to main landmarks
  this.child_cache_elements = [];  // This array is always empty for the title element

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TitleElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.TitleElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.TitleElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.TitleElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

 cache_nls.addPropertyIfDefined(properties, this, 'name');
 cache_nls.addPropertyIfDefined(properties, this, 'name_for_comparison');
 cache_nls.addPropertyIfDefined(properties, this, 'main_type');
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.TitleElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};



/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.TitleElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.TitleElement
 *
 * @desc Returns a text string representation of the title element 
 *
 * @return {String} Returns string represention the title element object
 */
  
OpenAjax.a11y.cache.TitleElement.prototype.toString = function () {
  var str = "title: ";
  
  if (this.name.length) str += this.name;  
  else str += 'no content';
  
  return str;
};


/* ---------------------------------------------------------------- */
/*                       PageElementHeadingsLandmarks                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor PageElementHeadingsLandmarks
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a body element object used to hold information about a title element
 *
 * @param  {DOMelement}   dom_element      - The dom element object representing the heading element 
 * @param  {MainElement}  parent_landmark  - This is always null since this is the root element
 *
 * @property  {DOMElement}   dom_element      - Reference to the dom element representing the optgroup element
 * @property  {String}       cache_id         - String that uniquely identifies the cache element object in the cache
 * @property  {Number}       document_order   - Ordinal position of the title and main cache items in the document to other title and main cache items
 *
 * @property  {MainElement}  parent_landmark  - Information about the parent main landmark (NOTE: can be null)
 *
 * @property  {Array}  child_cache_elements  - List of child cache title element, main landmarks and h1 heading element objects as part of cache title and main elements tree  
 *
 * @property  {Number}   type  -  Constant representing the body element 
 *
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks = function (dom_element, parent_landmark) {

  this.dom_element     = dom_element;
  this.cache_id        = "page_heading";
  this.document_order  = 0;
  this.is_page_element = true;

  this.main_type          = OpenAjax.a11y.MAIN.BODY_ELEMENT;

  this.child_cache_elements = []; // this is always empty for the body element

  this.parent_landmark    = parent_landmark; // restricted to main landmarks
  
  this.num_main_landmarks = 0;          // are defined in landmark rules
  this.num_visible_main_landmarks = 0;  // are defined in landmark rules
  
}; 

/**
 * @method addChildMainElement
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Adds a main landmark  object to the tree of title and main elements  
 *
 * @param {MainElement}  main_element  -  Main landmark element object to add 
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.addChildMainElement = function (main_element) {

  if (main_element) {
    this.child_cache_elements.push(main_element); 
  }  

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};



/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.PageElementHeadingsLandmarks
 *
 * @desc Returns a text string representation of the title element 
 *
 * @return {String} Returns string represention the title element object
 */
  
OpenAjax.a11y.cache.PageElementHeadingsLandmarks.prototype.toString = function () {
  return "page";  
};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                            ImageCache                            */
/* ---------------------------------------------------------------- */

/**
 * @constructor ImagesCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates cache object representing information related to images in a document
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {Array}    image_elements  - List of image element objects in the document 
 * @property {Number}   length          - Number of image element objects in the list 
 *
 * @property {String}   sort_property   - Image element object property the list of link objects is sorted by
 * @property {Boolean}  sort_ascending  - true if list is sorted in ascending order, otherwise false
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */
  
OpenAjax.a11y.cache.ImagesCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  this.up_to_date = false;
 
  this.image_elements = [];
  this.length = 0;

  this.sort_property  = 'document_order';
  this.sort_ascending = true;

}; 

/**
 * @method addImageElement
 * 
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Adds a image element to the list of image elements and generates a cache id for the object.
 *
 * @param  {ImageElement}  image_element  - image element object to add 
 *
 * @return {Number} Returns the length of the list of image element objects
 */

OpenAjax.a11y.cache.ImagesCache.prototype.addImageElement = function (image_element) {

  // item must exist and have the position property
  if (image_element) {
    this.length = this.length + 1;
    image_element.cache_id = "image_" + this.length; 
    image_element.document_order = this.length;
    this.image_elements.push(image_element);
  } 

  return this.length;

};

/**
 * @deprecated getImageElementByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Finds the the image element object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of image element object
 *
 * @return {ImageElement | null} Returns cache image element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.ImagesCache.prototype.getImageElementByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Finds the the image element object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of image element object
 *
 * @return {ImageElement | null} Returns cache image element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.ImagesCache.prototype.getItemByCacheId = function (cache_id) {

  var i;
  var image_elements_len = this.image_elements.length;

  if (cache_id && cache_id.length) {  
    for (i=0; i < image_elements_len; i++) {
      if (this.image_elements[i].cache_id == cache_id) {
        return this.image_elements[i];
      }
    } // end loop
  } 

 return null;
};


/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Resests the ImagesCache object properties and empties all the lists and arrays 
 */

OpenAjax.a11y.cache.ImagesCache.prototype.emptyCache = function () {

  this.image_elements = [];
  this.sort_property = 'document_order';
  this.up_to_date = false;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Updates the images cache object by checking to see if a dom element
 *          should be added to the cache
 *  
 * @param  {DOMElement}   dom_element   - dom element object to check for inclusion in images cache
 */
 
OpenAjax.a11y.cache.ImagesCache.prototype.updateCacheItems = function (dom_element) {

  if ((dom_element.tag_name === 'img') ||
      (dom_element.tag_name === 'area') ||
      ((typeof dom_element.role === 'string') && (dom_element.role === 'img'))) {

    var image_element = new OpenAjax.a11y.cache.ImageElement(dom_element, this.dom_cache.base_url);

    this.getNameForImage(image_element);
    
    this.addImageElement(image_element);
  
  }
  
};

/**
 * @method traverseDOMElementsForImageElements
 *
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Traverses DOMElement objects in the tree to update the images cache 
 *
 * @param  {DOMElement}  dom_element - dom element object to check for inclusion in images cache
 */
 
OpenAjax.a11y.cache.ImagesCache.prototype.traverseDOMElementsForImageElements = function (dom_element) {

  if (!dom_element) return;

  if (dom_element.type == Node.ELEMENT_NODE) {

    this.updateCacheItems(dom_element);
  
    for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
      this.traverseDOMElementsForImageElements(dom_element.child_dom_elements[i]);
    } // end loop
  }  
  
}; 

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.ImagesCache
 *
 * @desc Traverses the DOMElements to update the images cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the links cache are disabled, this cache would 
 *       not be updated
 */
 
OpenAjax.a11y.cache.ImagesCache.prototype.updateCache = function () {
  var i;
  var children = this.dom_cache.element_cache.child_dom_elements;
  var children_len = children.length;
 
  for (i=0; i < children_len; i++) {
    this.traverseDOMElementsForImageElements(children[i]);
  }  

  this.up_to_date = true;
};

/**
 * @method getNameFromARIALabel
 *
 * @memberOf OpenAjax.a11y.cache.DOMCache
 *
 * @desc Calculates a computed accessible name based on ALT attribute and ARIA label properties
 *
 * @param {Object} image - Image cache element object
 */

OpenAjax.a11y.cache.ImagesCache.prototype.getNameForImage = function (image) {

  var SOURCE = OpenAjax.a11y.SOURCE;

  var accessible_name = "";
  var accessible_name_source = SOURCE.NONE;
  var de = image.dom_element;
  
  if (de.aria_labelledby && de.aria_labelledby.length) {
    accessible_name = this.dom_cache.element_with_id_cache.getTextFromIds(de.aria_labelledby);
    accessible_name_source = SOURCE.ARIA_LABELLEDBY;
  }
  else if (de.aria_label && de.aria_label.length) {
    accessible_name = de.aria_label;
    accessible_name_source = SOURCE.ARIA_LABEL;
  }
  else if (de.has_alt_attribute) {
    accessible_name = de.alt;
    accessible_name_source = SOURCE.ALT_ATTRIBUTE;
  } 
  else if (de.title) {
    accessible_name = de.title;
    accessible_name_source = SOURCE.TITLE_ATTRIBUTE;
  }

  image.accessible_name = accessible_name;
  image.accessible_name_length = accessible_name.length;
  image.accessible_name_source = accessible_name_source;
  image.accessible_name_for_comparison = accessible_name.normalizeSpace().toLowerCase();

};

/* ---------------------------------------------------------------- */
/*                            ImageElement                          */
/* ---------------------------------------------------------------- */

/**
 * @constructor ImageElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates image element object representing information related to an image or area element on a web page
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the image or area element 
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the image or area element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the image or area element in the document in relationship to other image or area elements
 *
 * @property  {Boolean}     is_image        - True if the role of the image is an image, otherwise false (i.e. through use of role attribute) 
 * @property  {Boolean}     is_presemtation - True if the role of the image has been changed to presentation, otherwise false  
 *
 * @property  {String}   source             - The url in the src property of an image element or href property of an area element 
 * @property  {Boolean}  src_is_a_file_name - The filename is an image file and not a data base or other programatic reference
 * @property  {String}   file_name          - The filename of the image
 *
 * @property  {String}   longdesc           - The url in the longdesc property of an image element  
 * @property  {Boolean}  has_longdesc       - Does the image have a longdesc attribute  
 * @property  {Number}   longdesc_is_broken - Constant representing if the url is broken or untested  
 * @property  {String}   longdesc_url       - The full URL of the longdesc attribute  
 *
 * @property  {String}   alt                   - Calculated accessible name of the link 
 * @property  {String}   alt_for_comparison   - Accessible name for comparison (i.e. lowercase, trimmed and space normalized)
 * @property  {Number}   alt_length           - Number of images that are descendents of the link
 *  
 * @property  {Number}   height  - Height of the image in pixels
 * @property  {Number}   width   - Width of the image in pixels
 */
 
OpenAjax.a11y.cache.ImageElement = function (dom_element, base_url) {

  var alt_value;

  if (!dom_element) return null;  

  var node = dom_element.node;
 
  this.dom_element    = dom_element;
  this.cache_id       = "";
  this.document_order = 0;
  
  this.source    = "";
  this.href      = "";
  this.file_name = "";
  this.is_image = true;
  this.is_presentation = false;
  
  if (dom_element.has_role && dom_element.role != 'img') this.is_image = false;
  if (dom_element.has_role && dom_element.role === 'presentation') this.is_presentation = true;

//  OpenAjax.a11y.logger.debug("Image element: " + dom_element.toString() + " has: " + dom_element.has_role + " role: " + dom_element.role  + " image: " + this.is_image + " presentation: " + this.is_presentation);

  if (dom_element.tag_name == 'img') {
  
    if (node.src) this.source = node.src;
    
    var pos = this.source.lastIndexOf('/');    
    
    var file_name = "";
    this.src_is_a_file_name = false;
    
    if (this.source.length && pos >= 0 ) {
      file_name = this.source.substring((pos+1)).toLowerCase();
      
      if ((file_name.indexOf('.png') >= 0) ||
          (file_name.indexOf('.jpg') >= 0) ||
          (file_name.indexOf('.jpeg') >= 0) ||
          (file_name.indexOf('.gif') >= 0)) this.src_is_a_file_name = true;
    }  
  
    this.file_name = file_name;
  }
  
  if (dom_element.tag_name === 'area') {
    this.href  = node.href;
  }

  this.accessible_name = null;
  this.accessible_name_length = null;
  this.accessible_name_source = OpenAjax.a11y.SOURCE.NONE;
  this.accessible_name_for_comparison = null;

  this.longdesc = node.getAttribute('longdesc');
  
  if (this.longdesc) {

    this.longdesc_url = this.longdesc;

    if (this.longdesc.indexOf('http:') == -1 ) {
      this.longdesc_url = base_url + this.longdesc;
    }

    this.has_longdesc = true;
    this.longdesc_is_broken = OpenAjax.a11y.util.urlExists(this.longdesc_url);
  }
  else {
    this.has_longdesc = false;
    this.longdesc     = null;
    this.longdesc_is_broken = null;
  }


  this.height   = node.offsetHeight;
  this.width    = node.offsetWidth;

  return this;
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.ImageElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.ImageElement.prototype.getStyle = function () {

  return  this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.ImageElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var attributes = this.dom_element.getAttributes(unsorted);
     
  cache_nls.addPropertyIfDefined(attributes, this, 'title');
  cache_nls.addPropertyIfDefined(attributes, this, 'alt');
  cache_nls.addPropertyIfDefined(attributes, this, 'aria-label');
  cache_nls.addPropertyIfDefined(attributes, this, 'aria-labelledby');
  cache_nls.addPropertyIfDefined(attributes, this, 'longdesc');
  
  return attributes;
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.ImageElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
  
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_name');
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_name_source');
  cache_nls.addPropertyIfDefined(properties, this, 'height');
  cache_nls.addPropertyIfDefined(properties, this, 'width');
  cache_nls.addPropertyIfDefined(properties, this, 'document_order');
  cache_nls.addPropertyIfDefined(properties, this, 'has_longdesc');
  cache_nls.addPropertyIfDefined(properties, this, 'longdesc_url');
  cache_nls.addPropertyIfDefined(properties, this, 'longdesc_is_broken');
  
  return properties;
  
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.ImageElement.prototype.getCachePropertyValue = function (property) {

//  OpenAjax.a11y.logger.debug("Image property: " + property + " value= " + this[property]);

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};  

/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.ImageElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};


/**
 * @method getAltTextNLS
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Returns an object with an NLS localized string and style properties
 *       If alt attribute is empty a empty alt text message will the returned 
 *
 * @return {String | Object} Returns a String if the alt attribute has content, 
 *                            but if label is empty it returns an object 
 *                            with a 'label and 'style' property
 */

OpenAjax.a11y.cache.ImageElement.prototype.getAltTextNLS = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var alt_style = {};
  
  if (this.dom_element.has_alt_attribute) {
    if (this.alt_length) {
      return this.alt;
    }
    else {
      return cache_nls.getNLSEmptyAltTextMessage();
    }
  }
  else {
    return cache_nls.getNLSMissingAltMessage();
  }
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.ImageElement
 *
 * @desc Creates a text string representation of the image element object 
 *
 * @return {String} Returns a text string representation of the image element object
 */
 
 OpenAjax.a11y.cache.ImageElement.prototype.toString = function () {
   var str = this.dom_element.tag_name;
   
   if (this.dom_element.computed_style.is_visible_onscreen == OpenAjax.a11y.VISIBILITY.HIDDEN) {
     str += " (hidden) : ";
   } 
   else {
     str += " (" + this.height + "x" + this.width + ") : ";
   }  
   
   if (this.src_is_a_file_name) {
     str += this.file_name;
   }
   else {
     str +=  "source is not a file name";   
   }
   
   return str;
};


/*
 * Copyright 2011, 2012 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                            LanguagesCache                        */
/* ---------------------------------------------------------------- */

/**
 * @constructor LanguagesCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructor for languages cache object which contains a list of 
 *    language items representing the language changes of content 
 *    in the in a document. The language items also contain a list of all the 
 *    dom element objects that share the same language
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {String}    sort_property   - Property of language item that the list is sorted on  
 * @property {Boolean}   sort_ascending  - true if list is sorted by ascending values, otherwsie false 
 *
 * @property {Array}    language_items  - List of language items 
 * @property {Number}   length          - Number of language items in list 
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */
OpenAjax.a11y.cache.LanguagesCache = function (dom_cache) {
    
  this.dom_cache  = dom_cache;
  this.up_to_date = false;
    
  this.language_items =[];
  this.length = 0;
        
  this.sort_property  = 'lang';
  this.sort_ascending = false;

};

/**
 * @method addLanguageItem
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Adds a DOM Element object with an language property to the langauge item list.
 *       If the abreviation item does not exist the function will create one
 *
 * @param {DOMElement}  dom_element  - dom element to add to a abbreviation list
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.addLanguageItem = function (dom_element) {
    
    var li; //language item
    var found = false;
    var language_items = this.language_items;
    var language_items_len = language_items.length; 
    
    for (var i = 0; i < language_items_len; i++) {
      if (dom_element.lang == language_items[i].language) {
        found = true;
        language_items[i].addDOMElement(dom_element);
        break;
      }
    }     
    if (!found) {
        li = new OpenAjax.a11y.cache.LanguageItem(dom_element);
        
        li.addDOMElement(dom_element);
        this.length += 1;
        this.cache_id = "lang_" + this.length;
        this.language_items.push(li);
    }
};

/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.languagesCache
 *
 * @desc Returns the language item object with the cache id
 *
 * @param {String}  cache_id  - cache id of the language item object
 *
 * @return {LanguageItem} Returns language item object if cache id found, otherwise null  
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.getItemByCacheId = function (cache_id) {
    
  var i, j;
  var li, de;
  var dom_elements, dom_elements_len;
  
  var language_items     = this.language_items;
  var language_items_len = language_items.length;
    
  if (cache_id && cache_id.length) {
  
    for (i = 0; i < language_items_len; i++) {
      li = language_items[i];
      
      if (li.cache_id == cache_id) return li;
      
      dom_elements     = li.dom_elements;
      dom_elements_len = dom_elements.length;
      
      for (j = 0; j < dom_elements_len; j++ ) {
        de = dom_elements[j];
        if (de.cache_id == cache_id) return de;
      } // end loop
    } // end loop  
  }
    
  return null;
  
};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Empties all the language items from the cache
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.emptyCache = function () {
    
    this.language_items.length = 0;
    this.up_to_date = false;
};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Updates the language cache object with information from a dom element object
 *       This is used during the creation of the cache and is used by the functions for
 *       either creating the cache all at one time or selectively
 *
 * @param {DOMElement}  dom_element  - DOM Element object to add to the language cache
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.updateCacheItems = function (dom_element) {
    
    if (dom_element.lang && dom_element.lang.length) {
      this.addLanguageItem(dom_element);
    }  
};

/**
 * @method traverseDOMElementsForLanguages
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Traverses the DOMElements to update the language cache
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.traverseDOMElementsForLanguages = function (dom_element) {
    
    var i;
    if (! dom_element) return;
    
    if (dom_element.type == Node.ELEMENT_NODE) {
        
        this.updateCacheItems(dom_element);
        
        for (i = 0; i < dom_element.child_dom_elements.length; i++) {
            this.traverseDOMElementsForLanguages(dom_element.child_dom_elements[i]);
        }
        // end loop
    }
};

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Traverses the DOMElements to update the language cache
 *    This function is used to update the language cache 
 *    when needed by a rule, it sets the up to date flag when done
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.updateCache = function () {
    var i;
    var children = this.dom_cache.element_cache.child_dom_elements;
    var children_len = children.length;
    
    for (i = 0; i < children_len; i++) {
        this.traverseDOMElementsForLanguages(children[i]);
    }
    
    this.up_to_date = true;
};


/**
 * @method sortLanguageItems
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Sorts languages by language property
 *
 * @param {Boolean}  ascending  - true if sort in ascending order; false in descending order
 *
 * @return {Boolean}  Returns true if list was sorted, false if not
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.sortLanguageItems = function(ascending) {

  var swapped = false;
  var temp = null;
  var i;

  if( !this.language_items || (this.language_items.length === 0)) {
    return false;
  } // endif

  this.sort_ascending = ascending;
 
  var language_items_len = this.language_items.length;

  if( ascending ) {
    do{
      swapped = false;
      for (i = 1; i < language_items_len; i++ ) {
        if (this.language_items[i-1].language > this.language_items[i].language) {
          // swap the values
          temp = this.language_items[i-1];
          this.language_items[i-1] = this.language_items[i];
          this.language_items[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < language_items_len; i++ ) {
        if (this.language_items[i-1].language < this.language_items[i].language) {
          // swap the values
          temp = this.language_items[i-1];
          this.language_items[i-1] = this.language_items[i];
          this.language_items[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  } 

  this.sort_property = 'language';
 
  return true;

}; 

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.LanguagesCache
 *
 * @desc Returns a text string representation of the language cache object 
 *
 * @return {String} Returns string represention the language cache object
 */

OpenAjax.a11y.cache.LanguagesCache.prototype.toString = function () {
    
    var i;
    
    var str = "\n\n Language Information\n";
    
    var list_length = this.language_items.length;
    
    for (i = 0; i < list_length; i++) {
        str += this.language_items[i].toString();
    }
    // end loop
    
    return str;
};

/* ---------------------------------------------------------------- */
/*                      LanguageItem                                */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor LanguageItem
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructor for languages item object which contains information
 *       about dom elements that share the same abbreviation 
 * 
 * @param  {String}  language  - language reference value
 *         
 * @property  {String}  language   - text of abbreviation
 * @property  {String}  cache_id   - String that uniquely identifies the cache element in the cache
 *
 * @property  {Array}   dom_elements  - List of dom elements associated with the language reference 
 * @property  {Number}  count         - Number of dom elements that share this language reference
 */
  
OpenAjax.a11y.cache.LanguageItem = function (language) {
    
  this.cach_id = "";
  
  this.language = language;
  this.dom_elements = [];
  this.count = 0;
    
};

/**
 * @method addDOMElement
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc  Adds a dom element object to the list of dom elements associated with this language reference 
 *
 * @param  {DOMElement} dom_element  - dom element object to add
 */

OpenAjax.a11y.cache.LanguageItem.prototype.addDOMElement = function (dom_element) {
    
    if (dom_element) {
      this.dom_elements.push(dom_element);
    }
};


/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.LanguageItem.prototype.getNodeResults = function () {
  return this.dom_text_nodes[0].getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.LanguageItem.prototype.getStyle = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
  
  cache_nls.addPropertyIfDefined(properties, this, 'is_large_font');
  cache_nls.addPropertyIfDefined(properties, this, 'color_contrast_ratio');
 
  cache_nls.addPropertyIfDefined(properties, this, 'color');
  cache_nls.addPropertyIfDefined(properties, this, 'background_color');
  cache_nls.addPropertyIfDefined(properties, this, 'background_image');
  cache_nls.addPropertyIfDefined(properties, this, 'background_repeat');
  cache_nls.addPropertyIfDefined(properties, this, 'background_position');

  cache_nls.addPropertyIfDefined(properties, this, 'font_family');
  cache_nls.addPropertyIfDefined(properties, this, 'font_size');
  cache_nls.addPropertyIfDefined(properties, this, 'font_weight');  
  
  return properties;
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.LanguageItem.prototype.getAttributes = function (unsorted) {

  return [];
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.LanguageItem.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
  
  cache_nls.addPropertyIfDefined(properties, this, 'language');
  
  return properties;
  
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.LanguageItem.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return null;
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.LanguageItem.prototype.getEvents = function () {
   
  return [];
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.LanguageItem
 *
 * @desc Returns a text string representation of the language reference object 
 *
 * @return {String} Returns string represention the language reference object
 */

OpenAjax.a11y.cache.LanguageItem.prototype.toString = function () {
    
    return "  Language: " + this.language + " (" + this.dom_elements.length + " elements)\n";
    
};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                            LinkCache                             */
/* ---------------------------------------------------------------- */

/**
 * @constructor LinksCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates cache object representing information related to links in a web page
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object       
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {Array}    area_elements  - List of area element objects in the document 
 * @property {Array}    link_elements  - List of link element objects in the document 
 * @property {Number}   length         - Number of link element objects in the list 
 *
 * @property {String}   this.sort_property   - Link element object property the list of link objects is sorted by
 * @property {Boolean}  this.sort_ascending  - true if list is sorted in ascending order, otherwise false
 *
 * @property {Array}    links_sorted_by_href  - List of link element object sorted by href values;
 * @property {Array}    links_sorted_by_name  - List of link element object sorted by there accessible name (i.e link text);
 *  
 * @property {Boolean}  sorted_by_href_ready  - True if list of link element objects sorted by href values is ready for use in rules
 * @property {Boolean}  sorted_by_name_ready  - True if list of link element objects sorted by name values is ready for use in rules
 */

OpenAjax.a11y.cache.LinksCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  this.up_to_date = false;
  
  this.area_elements = [];
  this.link_elements = [];
  this.length = 0;

  this.links_sorted_by_href = [];
  this.links_sorted_by_name = [];
  
  this.sorted_by_name_ready = false;
  this.sorted_by_href_ready = false;
  
}; 

/**
 * @method getLinkElementsSortedByName
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Returns a list of link elements sorted by accessible name property
 *
 * @return {Array}  Returns an array of LinkElement objects
 */

OpenAjax.a11y.cache.LinksCache.prototype.getLinkElementsSortedByName = function () {

  function compare(a,b) {
  
    return ((a.accessible_name_for_comparison > b.accessible_name_for_comparison) ||
            ((a.accessible_name_for_comparison === b.accessible_name_for_comparison) &&
             (a.href > b.href)));
  
  }
  
 
  if (!this.sorted_by_name_ready) {
  
    this.links_sorted_by_name.sort(compare);
        
    this.sorted_by_name_ready = true;
    
  }

/*
  OpenAjax.a11y.logger.debug( "Number of Links: " + this.links_sorted_by_name.length);

  for (i = 0; i < this.links_sorted_by_name.length; i++ ) {
   
    var l = this.links_sorted_by_name[i];
  
    OpenAjax.a11y.logger.debug( i + "  Name: " + l.accessible_name + "  Compare Name: " + l.accessible_name_for_comparison + " HREF: " + l.href );
  
  }
*/
  return this.links_sorted_by_name;

};

/**
 * @method getLinksThatShareTheSameName
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Returns a list of links that share the same accessible name
 *       links that are hidden are ignored
 *
 * @return  {Array} Returns at array of same name objects with the following properties:<br/> 
 *                  links: an array of link objects<br/>
 *                  same_names: True if all the links in the array share the same accessible name<br/>
 *                  names_count: Number of different accessible names in the links array<br/>
 */

OpenAjax.a11y.cache.LinksCache.prototype.getLinksThatShareTheSameName = function () {

  function checkForUniqueDescriptions(sns) {
  
    function compareDescriptions( a, b ) {
      return a.accessible_description_for_comparison < b.accessible_description_for_comparison;
    }
  
    var same_name = sns.links.sort(compareDescriptions);
    sns.unique_descriptions = true;
        
    for (var i = 1; i < same_name.length; i++) {
      var name1 = same_name[i-1];
      var name2 = same_name[i];
          
      sns.unique_descriptions = same_names.unique_descriptions && 
      ((name1.accessible_description_for_comparison !== name2.accessible_description_for_comparison) ||
       (name1.href === name2.href));
    }
    
  }



  var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
  
  var list_of_same_names = [];
  var same_names = null;
  
  var names = this.getLinkElementsSortedByName();
  var names_len = names.length;
  
  var i = 0;
  var j = 1;
  
  while (j < names_len) {
  
    var name1 = names[i];
    var name2 = names[j];
  
    if (name1.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
      i++;
      j++;
      continue;  
    }
    
    if (name2.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
      j++;
      continue;
    }  
  
    if (name1.accessible_name_for_comparison === name2.accessible_name_for_comparison) {
       
      if (same_names) {
       
        same_names.links.push(name2);
        
        if (name1.href !== name2.href) {
            same_names.same_hrefs = false;
            same_names.hrefs_count += 1;
        }  

      }
      else {
        same_names = {
          links       : [name1, name2],
          same_hrefs  : name1.href === name2.href,
          hrefs_count : (name1.href === name2.href) ? 1 : 2
        };  
      }
    }
    else {
      if (same_names) {
        checkForUniqueDescriptions(same_names);
        list_of_same_names.push(same_names);
        same_names = null;
      }
    }
    
   i = j; 
   j++; 
  }

  if (same_names) {
    checkForUniqueDescriptions(same_names);
    list_of_same_names.push(same_names);
  }  

/*
  OpenAjax.a11y.logger.debug( "Number of same name objects in list: " + list_of_same_names.length);

  for (i = 0; i < list_of_same_names.length; i++ ) {
    var item = list_of_same_names[i]; 
    OpenAjax.a11y.logger.debug( i  + " NAME: " + item.links[0].accessible_name + "  Number: " + item.links.length + "  Same HREF: " + item.same_hrefs);  
  }
*/
  return list_of_same_names;
  
};

/**
 * @method getLinkElementsSortedByHREF
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Returns a list of link elements sorted by accessible name property
 * 
 * @return {Array}  Returns an array of LinkElement objects
 */

OpenAjax.a11y.cache.LinksCache.prototype.getLinkElementsSortedByHREF = function () {

  function compare(a,b) {
  
    return ((a.href > b.href) ||
            ((a.href === b.href) &&
             (a.accessible_name_for_comparison > b.accessible_name_for_comparison)));
  
  }
  
 
  if (!this.sorted_by_href_ready) {
    this.links_sorted_by_href.sort(compare);
    this.sorted_by_href_ready = true;
    
    
  }

/*
  OpenAjax.a11y.logger.debug( "Number of Links: " + this.links_sorted_by_href.length);

  for (i = 0; i < this.links_sorted_by_href.length; i++ ) {
    var l = this.links_sorted_by_href[i]; 
    OpenAjax.a11y.logger.debug( i  + " HREF: " + l.href + "  Name: " + l.accessible_name + "  Compare Name: " + l.accessible_name_for_comparison );  
  }
*/
  return this.links_sorted_by_href;
  
};


/**
 * @method getLinksThatShareTheSameHREF
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Returns a list of link elements that share the same HREF value
 *       links that are hidden are ignored
 *
 * @return  {Array} Returns at array of same href objects, with the followin properties:<br/>
 *                  links: an array of link objects<br/>
 *                  same_names: True if all the links in the array share the same accessible name<br/>
 *                  names_count: Number of different accessible names in the links array<br/>
 */

OpenAjax.a11y.cache.LinksCache.prototype.getLinksThatShareTheSameHREF = function () {

  var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
  
  var list_of_same_hrefs = [];
  var same_hrefs = null;
  
  var hrefs = this.getLinkElementsSortedByHREF();
  var hrefs_len = hrefs.length;
  
  var i = 0;
  var j = 1;
  
  while (j < hrefs_len) {
  
    var href1 = hrefs[i];
    var href2 = hrefs[j];
  
    if (href1.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
      i++;
      j++;
      continue;  
    }
    
    if (href2.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
      j++;
      continue;
    }  
  
    if (href1.href ===  href2.href) {
       
      if (same_hrefs) {
       
        same_hrefs.links.push(href2);
        
        if (href1.accessible_name_for_comparison !== href2.accessible_name_for_comparison) {
          same_hrefs.same_names = false;
          same_hrefs.names_count += 1;
        }
        
      }
      else {
        same_hrefs = {
          links : [href1, href2],
          same_names : href1.accessible_name_for_comparison === href2.accessible_name_for_comparison,
          names_count : (href1.accessible_name_for_comparison === href2.accessible_name_for_comparison) ? 1 : 2
        };  
      }
    }
    else {
      if (same_hrefs) {
        list_of_same_hrefs.push(same_hrefs);
        same_hrefs = null;
      }
    }
    
   i = j; 
   j++; 
  }

  if (same_hrefs) list_of_same_hrefs.push(same_hrefs);
/*
  OpenAjax.a11y.logger.debug( "Number of DUP HREF objects: " + list_of_same_hrefs.length);

  for (i = 0; i < list_of_same_hrefs.length; i++ ) {
    var item = list_of_same_hrefs[i]; 
    OpenAjax.a11y.logger.debug( i  + " HREF: " + item.links[0].href + "  Number: " + item.links.length + "  Same Name: " + item.same_names);  
  }
*/
  return list_of_same_hrefs;
  
};


/**
 * @method addLinkElement
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Adds a link element to the list of link elements and generates a cache id for the object.
 *       Checks if the link has a duplicate href or name in the document 
 *
 * @param  {LinkElement}  link_element  - link element to add 
 *
 * @return {Number} Returns the length of the list of link elements
 */

OpenAjax.a11y.cache.LinksCache.prototype.addLinkElement = function (link_element) {

  // item must exist and have the position property
  if (link_element) {
    this.links_sorted_by_name.push(link_element);
    this.links_sorted_by_href.push(link_element);

    this.length = this.length + 1;
    link_element.cache_id = "link_" + this.length; 
    link_element.document_order = this.length;
    this.link_elements.push(link_element);
    
    if (link_element.dom_element.tag_name === 'area') {
      this.area_elements.push(link_element);
    }
  } 
  
  return this.length;
};

/**
 * @deprecated getLinkElementByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Finds the the link element object with the matching cache id
 *
 * @param  {String }  cache_id  - Cache id of link element object
 *
 * @return {LinkElement} Returns cache link element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.LinksCache.prototype.getLinkElementByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Finds the the link element object with the matching cache id
 *
 * @param  {String }  cache_id  - Cache id of link element object
 *
 * @return {LinkElement} Returns cache link element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.LinksCache.prototype.getItemByCacheId = function (cache_id) {

  var i;

  var link_elements_len = this.link_elements.length;

  if (cache_id && cache_id.length) {  
   for (i=0; i < link_elements_len; i++) {
     if (this.link_elements[i].cache_id == cache_id) {
       return this.link_elements[i];
     }
   } // end loop
 } 

 return null;
};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Resests the LinksCache object properties and empties all the lists and arrays 
 */
 
OpenAjax.a11y.cache.LinksCache.prototype.emptyCache = function () {

  this.link_elements = [];
  this.length = 0;
  this.sort_property = 'document_order';
  this.up_to_date = false;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Updates the links cache object by checking to see if a dom element
 *          should be added to the cache
 *  
 * @param  {DOMElement}   dom_element   - DOMElement object to check for inclusion in links cache
 */
 
OpenAjax.a11y.cache.LinksCache.prototype.updateCacheItems = function (dom_element) {

  var link_element;

  if ((dom_element.tag_name === 'a' && !dom_element.is_widget) ||
      (dom_element.tag_name === 'area' && !dom_element.is_widget) ||
      ((typeof dom_element.role === 'string') && (dom_element.role === 'link'))) {
      
        dom_element.is_interactive = true;
      
        link_element = new OpenAjax.a11y.cache.LinkElement(dom_element);    
    
        this.dom_cache.getNameForLink(link_element);
    
        this.dom_cache.links_cache.addLinkElement(link_element);
  }
   
};

/**
 * @method traverseDOMElementsForLinkElements
 *
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Traverses dom element objects in the tree to update the links cache 
 *
 * @param  {DOMElement}  dom_element - dom element object to check for inclusion in links cache
 */
 
OpenAjax.a11y.cache.LinksCache.prototype.traverseDOMElementsForLinkElements = function (dom_element) {
 
  var i;

  if (!dom_element) return;

  if (dom_element.type == Node.ELEMENT_NODE) {

    this.updateCacheItems(dom_element);
  
    for (i = 0; i < dom_element.child_dom_elements.length; i++) {
      this.traverseDOMElementsForLinkElements(dom_element.child_dom_elements[i]);
    } // end loop
  }  
  
}; 


/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.LinksCache
 *
 * @desc Traverses the DOMElements to update the links cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the links cache are disabled, this cache would 
 *       not be updated
 */
 
OpenAjax.a11y.cache.LinksCache.prototype.updateCache = function () {

  var i;
  var children = this.dom_cache.element_cache.child_dom_elements;
  var children_len = children.length;
 
  for (i=0; i < children_len; i++) {
    this.traverseDOMElementsForLinkElements(children[i]);
  }  

  this.up_to_date = true;
 
};


/* ---------------------------------------------------------------- */
/*                            LinkElement                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor LinkElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates link element object representing information related to an a or area element on a web page
 *
 * @param  {DOMElement}   dom_element   - The dom element object representing the a or area element 
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the a or area element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the a or area element in the document in relationship to other a or area elements
 *
 * @property  {String}   href    - The absolute path of the href value 
 * @property  {Boolean}  is_url  - true if href is a url, otherwise false (i.e. internal link or broken)
 * @property  {Boolean}  is_link - true if href is a internal or exteranl link, otherwise
 * @property  {Number}   link_type - Type of link contstant
 *
 * @property  {String}   tab_index  - value of the tabindex attribute
 * @property  {String}   name_attr  - value of the name attribute
 * @property  {String}   target     - value of the target attribute
 *
 * @property  {String}   name                  - Calculated accessible name of the link 
 * @property  {String}   name_for_comparison   - Accessible name for comparison (i.e. lowercase, trimmed and space normalized)
 *  
 * @property  {Number}   height  - Height of the link in pixels
 * @property  {Number}   width   - Width of the link in pixels
 */
 
OpenAjax.a11y.cache.LinkElement = function (dom_element) {

  function getTypeOfLink(href, name, id) {
  
    href = href.toLowerCase();
    
    if (typeof href != 'string') return OpenAjax.a11y.LINK_TYPE.OTHER;
   
    if (href.length === 0) { 
      if ((name && name.length) || (id && id.length)) 
        return OpenAjax.a11y.LINK_TYPE.TARGET;
      else  
        return OpenAjax.a11y.LINK_TYPE.EMPTY;
    }    

    if (href === '#') return OpenAjax.a11y.LINK_TYPE.EMPTY;

    if (href.indexOf('http://') >= 0) return OpenAjax.a11y.LINK_TYPE.HTTP;
    else
      if (href.indexOf('https://') >= 0) return OpenAjax.a11y.LINK_TYPE.HTTPS;
      else
        if (href.indexOf('ftp://') >= 0) return OpenAjax.a11y.LINK_TYPE.FTP;
        else
          if (href.indexOf('ftps://') >= 0) return OpenAjax.a11y.LINK_TYPE.FTPS;
          else 
            if (href.indexOf('file://') >= 0) return OpenAjax.a11y.LINK_TYPE.FILE;
            else 
              if (href.indexOf('javascript:') >= 0) return OpenAjax.a11y.LINK_TYPE.JAVASCRIPT;
              else 
                if (href.indexOf('mailto:') >= 0) return OpenAjax.a11y.LINK_TYPE.MAILTO;
                else 
                  if (href[0] === '#') return OpenAjax.a11y.LINK_TYPE.INTERNAL;
 
    return OpenAjax.a11y.LINK_TYPE.HTTP;
  }


  function testIfHrefIsURL(url) {
  
    if (typeof href != 'string') return false;
  
    if (url.indexOf('http://') >= 0) return true;
    else
      if (url.indexOf('https://') >= 0) return true;
      else
        if (url.indexOf('ftp://') >= 0) return true;
        else
          if (url.indexOf('ftps://') >= 0) return true;
          else 
            if (url.indexOf('file://') >= 0) return true;
 
    return false;
  }

  if (!dom_element.node) return;

  var href = dom_element.node.href;

  if ((typeof dom_element.role === 'string') &&
      (dom_element.role === 'link') && 
      (href !== 'string')) href = "javascript:onclick";

  this.dom_element    = dom_element;
  this.cache_id       = "";
  this.document_order = 0;
  
  this.role = dom_element.role;
 
  this.href  = href;
  this.is_url = testIfHrefIsURL(href);
  
  if (this.is_url) { 
    this.is_broken = OpenAjax.a11y.util.urlExists(href);
  }
  else {
    this.is_broken = OpenAjax.a11y.URL_RESULT.NOT_A_URL;
  }

  this.tab_index = dom_element.node.tabIndex;
  
  this.name_attribute = dom_element.node.getAttribute("name");
  this.is_target = this.name_attribute && (this.name_attribute.length > 0);

  var link_type = getTypeOfLink(href, this.name_attribute, dom_element.id);
  
  this.link_type = link_type;
  
  this.is_link = false;

  if ((link_type !== OpenAjax.a11y.LINK_TYPE.OTHER) &&
      (link_type !== OpenAjax.a11y.LINK_TYPE.TARGET) &&
      (link_type !== OpenAjax.a11y.LINK_TYPE.EMPTY)) this.is_link = true;
  
  this.target  = dom_element.node.getAttribute("target");

  var ano = dom_element.getTextObject();
  
  var cs = dom_element.computed_style;
   
  // If the link is an image, use the image height and width
  if (ano.height > cs.height) cs.height = ano.height;
  if (ano.width > cs.width) cs.width = ano.width;

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.LinkElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.LinkElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.LinkElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  cache_nls.addPropertyIfDefined(attributes, this, 'href');

  cache_nls.addPropertyIfDefined(attributes, this, 'tab_index');
  cache_nls.addPropertyIfDefined(attributes, this, 'name_attribute');
  cache_nls.addPropertyIfDefined(attributes, this, 'target');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.LinkElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'accessible_name');
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_name_for_comparison');
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_description');
  cache_nls.addPropertyIfDefined(properties, this, 'accessible_description_for_comparison');

  cache_nls.addPropertyIfDefined(properties, this, 'is_broken');
  cache_nls.addPropertyIfDefined(properties, this, 'is_url');
  cache_nls.addPropertyIfDefined(properties, this, 'is_target');
  cache_nls.addPropertyIfDefined(properties, this, 'link_type');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.LinkElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.LinkElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method getLinkType
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Returns an array of style items 
 *
 * @return {String} Returns a NLS string representing the type of link
 */

OpenAjax.a11y.cache.LinkElement.prototype.getLinkType = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  return cache_nls.getValueNLS('link_type', this.link_type);
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.LinkElement
 *
 * @desc Creates a text string representation of the link element object 
 *
 * @return {String} Returns a text string representation of the link element object
 */
 
 OpenAjax.a11y.cache.LinkElement.prototype.toString = function () {
  
   var str = "";
   
   if ((this.dom_element.tag_name === 'a') || (this.dom_element.tag_name === 'area')) {
     str = this.dom_element.tag_name + " : " + this.accessible_name; 
   }
   else {
     str = this.dom_element.tag_name + "[role='link']: " + this.accessible_name;    
   }
   return str;
 };


/*
 * Copyright 2011, 2012 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
 
/* ---------------------------------------------------------------- */
/*                              ListInfo                            */
/* ---------------------------------------------------------------- */

/**
 * @constructor ListInfo
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a list information object for preserving the current list information 
 *        when traversing the DOM
 *
 * @param {ListInfo} list_info - Current list information object
 *
 * @property {ListElement | ContainerElement}  list_element      - Parent container list or container element object 
 * @property {ContainerElement}                container_element - Parent container element object 
 * @property {LandmarkElement}                 parent_landmark  - Parent landmark element object 
 */

OpenAjax.a11y.cache.ListInfo = function (list_info) {

  if (list_info) {
    this.list_element      = list_info.list_element;
    this.container_element = list_info.container_element;
    this.parent_landmark   = list_info.parent_landmark;
  }
  else {
    this.list_element      = null;
    this.container_element = null;
    this.parent_landmark   = null;
  }

};

/* ---------------------------------------------------------------- */
/*                            ListsCache                            */
/* ---------------------------------------------------------------- */

/**
 * @constructor ListsCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Constructor for lists cache object which contains a list of 
 *    list items representing the list (i.e ul, ol , dl, li, dt and dd) 
 *    elements defined in a document. 
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {Array}    child_cache_elements  - Root array of the tree representation of the list elements in the document 
 *
 * @property {Array}   container_elements  - List of the container element objects in the document that are not children of a container item 
 * @property {Array}   listitem_elements   - List of the listitem element objects  
 * @property {Number}  length              - Number of containter element objects in list 
 *
 * @property {String}   sort_property   - Property of contanter element objectthe list is sorted on  
 * @property {Boolean}  sort_ascending  - true if list is sorted by ascending values, otherwise false 
 *
 * @property {Number}  landmark_count   - Number of containter element objects in list 
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */

OpenAjax.a11y.cache.ListsCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  this.up_to_date = false;
  
  this.child_cache_elements = []; 

  this.container_elements = [];  
  this.listitem_elements = [];  
  this.length = 0;

  this.sort_property  = 'document_order';
  this.sort_ascending = true;

  this.landmark_count  = 0;
  
};

/** 
 * @method addContainerElement
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Adds a container element object to the list of container elements  
 *
 * @param  {ContainerElement} container_element   - Container element object to add 
 *
 * @return  {Number} Returns the number of container element objects in the list of container element objects
 */

OpenAjax.a11y.cache.ListsCache.prototype.addContainerElement = function (container_element) {

  if (container_element) {
    this.length += 1;
    container_element.document_order = this.length;
    container_element.cache_id = "con_" + this.length;
    this.container_elements.push(container_element);
    return true;
  }

  return false;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Update the ListsCache by checking to see if the current
 *       DOMElement is a list-related element and that consequently
 *       a new list element object should be added to this cache.
 *
 * @param  {DOMElement}   dom_element  - dom element object to check for inclusion in lists cache
 * @param  {ListInfo}     list_info    - Information about the current list relationships in the DOM
 *
 * @return {ListInfo}  Returns updated list information object 
 */

OpenAjax.a11y.cache.ListsCache.prototype.updateCacheItems = function (dom_element, list_info) {

  var li = new OpenAjax.a11y.cache.ListInfo(list_info);

  // check whether we need to add a new ListElement
  switch (dom_element.tag_name) {
  
  case 'ul':
  case 'ol':
  case 'dl':
  
    var ce = new OpenAjax.a11y.cache.ContainerElement(dom_element, list_info);
    
    if (!list_info.container_element) this.addContainerElement(ce);

    if (list_info.list_element) {
      list_info.list_element.addChildElement(ce);
    }
    else {
      this.addChildElement(ce);
    }

    li.container_element = ce;
    li.list_element      = ce;
    break;

  case 'li':
  case 'dt':
  case 'dd':

    var le = new OpenAjax.a11y.cache.ListElement(dom_element, list_info.container_element);

    if (list_info.container_element) list_info.container_element.addListElement(le);

    if (list_info.list_element) {
      list_info.list_element.addChildElement(le);
    }
    else {
      this.addChildElement(le);
    }
    
    this.listitem_elements.push(le);
    
    li.list_element = le;

    break;

  case 'a':
  
    if (list_info.list_element &&
        list_info.list_element.link_count &&
        dom_element.node.href && 
        dom_element.node.href.length) {
      list_info.list_element.link_count += 1;
    }

    break;
  
  default:
    break;
  
  } // end switch
  
  if ((typeof dom_element.role === 'string') &&
      ((dom_element.role == 'region')    ||
       (dom_element.role == 'main')     || 
       (dom_element.role == 'navigation')  ||
       (dom_element.role == 'search')    ||
       (dom_element.role == 'applicaton')  ||
       (dom_element.role == 'banner')    ||
       (dom_element.role == 'complementary') ||
       (dom_element.role == 'contentinfo')  ||
       (dom_element.role == 'form'))) {
   
    le = new OpenAjax.a11y.cache.LandmarkElement(dom_element, list_info.parent_landmark);   
    
    le.cache_id = "listLandmark_" + this.landmark_count;
    
    this.landmark_count += 1;

    this.dom_cache.getNameFromARIALabel(le);

    if (list_info.list_element) {
      list_info.list_element.addChildElement(le);
    }
    else {
      this.addChildElement(le);
    }
  
    li.parent_landmark = le;
    li.list_element    = le;
    
  }

  return li;

};

/**
 * @method traverseDOMElementsForListElements
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Traverses the DOMElements to update the abbreviation cache
 */
 
OpenAjax.a11y.cache.ListsCache.prototype.traverseDOMElementsForListElements = function (dom_element, list_info) {
 
  var i;
  var li;

  if (!dom_element) return;

  if (dom_element.type == Node.ELEMENT_NODE) {

    li = this.updateCacheItems(dom_element, list_info);
  
    for (i = 0; i < dom_element.child_dom_elements.length; i++ ) {
      this.traverseDOMElementsForListElements(dom_element.child_dom_elements[i], li);
    } // end loop
  
  }  
  
}; 

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Traverses the DOMElements to update the list cache
 *    This function is used to update the list cache 
 *    when needed by a rule, it sets the up to date flag when done
 */
 
OpenAjax.a11y.cache.ListsCache.prototype.updateCache = function () {

 var i;
 var children = this.dom_cache.element_cache.child_dom_elements;
 var children_len = children.length;
 
 var list_info = new OpenAjax.a11y.cache.ListInfo(null);
  
 for (i = 0; i < children_len; i++) {
  this.traverseDOMElementsForListElements(children[i], list_info);
 }  
 
 this.up_to_date = true;
};

/**
 * @method addChildElement
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Add a top-level list element object to the lists cache
 *
 * @param {ContainerElement | ListElement | LandmarkElement } list_element - list cache element object to add to the list cache
 *
 * @return {boolean} indicating success or failure
 */

OpenAjax.a11y.cache.ListsCache.prototype.addChildElement = function (list_element) {

  if (list_element) {
    this.child_cache_elements.push(list_element);
    return true;
  }

  return false;

};

/**
 * @deprecated getListElementByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc retrieve list element from lists cache based on its cache id
 *
 * @param  {String}  cache_id  -  cache id of the list cache element object to find
 *
 * @return {ListElement} Returns list cache object if cache id found, otherwise null 
 */

OpenAjax.a11y.cache.ListsCache.prototype.getListElementByCacheId = function (cache_id) {
 return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc retrieve list element from lists cache based on its cache id
 *
 * @param  {String}  cache_id  -  cache id of the list cache element object to find
 *
 * @return {ListElement} Returns list cache object if cache id found, otherwise null 
 */

OpenAjax.a11y.cache.ListsCache.prototype.getItemByCacheId = function (cache_id) {

  function findCacheID(child_elements) {

    var i; // loop counter
    var max; // loop control
    var le;
    var res;

    max = child_elements.length;
    for (i = 0; i < max; i++) {
      le = child_elements[i];
      if (le.cache_id === cache_id) {
        return le;
      }
      else {
        res = findCacheID(le.child_cache_elements);
        if (res) return res;
      }
    }
      
    return null;
  }

  return findCacheID(this.child_cache_elements);
  
};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Empties all the properties of the list cache 
 */

OpenAjax.a11y.cache.ListsCache.prototype.emptyCache = function () {

  this.dom_cache = null;
  this.up_to_date = false;
  
  this.child_elements     = []; 

  this.container_elements = [];  
  this.length = 0;

  this.sort_property  = 'document_order';
  this.sort_ascending = true;
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.ListsCache
 *
 * @desc Returns a text string representation of the lists cache object 
 *
 * @return {String} Returns string represention the lists cache object
 */

OpenAjax.a11y.cache.ListsCache.prototype.toString = function () {

 var str ="\n\nList Information\n";

 var list_length = this.container_elements.length;
 
 for (var i=0; i < list_length; i++ ) {
  str += this.container_elements[i].toString();  
 } // end loop

 return str;
};

/* ---------------------------------------------------------------- */
/*                            ListElement                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor ListElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Cache object to be inserted into ListsCache; corresponds to
 *       either a LI, DT, DD element in the DOM
 *
 * @param  {DOMelement}        dom_element       - The dom element object representing the input element 
 * @param  {ContainerElement}  parent_container  - Reference to the container element the list element is contained in
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the list element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the list element in the document
 *
 * @property  {ContainerElement}  parent_container  - Reference to the container element the list element is contained in
 * @property  {Number}            list_type         - Type of list cache element object
 *
 * @property  {Array}   child_cache_elements  - Array of child cache list elements as part of list cache tree 
 *
 * @property  {Number}  link_count    - Number of links in this list element
 */

OpenAjax.a11y.cache.ListElement = function (dom_element, parent_container) {

  this.dom_element    = dom_element;
  this.cache_id       = "";
  this.document_order = 0;
  
  this.parent_container  = parent_container;
  this.list_type = OpenAjax.a11y.LIST.ITEM;

  this.child_cache_elements = [];
  this.link_count = 0;

};

/**
 * @method addChildElement
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Add a list element object to the tree of list cache items 
 *
 * @param {ContainerElement | ListElement | LandmarkElement } list_element - list cache element object to add to the list cache
 *
 * @return {boolean} indicating success or failure
 */

OpenAjax.a11y.cache.ListElement.prototype.addChildElement = function (list_element) {

  if (list_element) {
    this.child_cache_elements.push(list_element);
    return true;
  }

  return false;

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.ListElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.ListElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.ListElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.ListElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);

  cache_nls.addPropertyIfDefined(properties, this, 'list_type');

  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.ListElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.ListElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};



/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.ListElement
 *
 * @desc Returns a text string representation of the list item object 
 *
 * @return {String} Returns string represention the list item object
 */

OpenAjax.a11y.cache.ListElement.prototype.toString = function () {

 return "List Item " + this.document_order + ": " + this.dom_element.getText(); 
 
};

/* ---------------------------------------------------------------- */
/*                           ContainerElement                       */
/* ---------------------------------------------------------------- */

/**
 * @constructor ContainerElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Cache object to be inserted into ListsCache; corresponds to
 *       either a OL, UL, DL element in the DOM
 *
 * @param  {DOMelement}  dom_element  - The dom element object representing the input element 
 * @param  {ListInfo}    list_info    - Current list information about 
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the container element
 * @property  {String}      cache_id        - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the container element in the document
 *
 * @property  {ContainerElement}  parent_container  - Reference to the container element the container element is contained in
 * @property  {LandmarkElement}   parent_landmark   - Reference to the landmark element the container element is contained in
 * @property  {Number}            list_type         - Type of list cache element object
 *
 * @property  {Array}   child_cache_elements  - Array of child cache list elements as part of list cache tree 
 */

OpenAjax.a11y.cache.ContainerElement = function (dom_element, list_info) {

  this.dom_element    = dom_element;
  this.cache_id       = "";
  this.document_order = 0;
  
  this.parent_container  = list_info.parent_container;
  this.parent_landmark   = list_info.parent_landmark;
  
  this.list_type = OpenAjax.a11y.LIST.CONTAINER;

  this.child_cache_elements = [];
  this.link_count = 0;

  this.list_elements = [];
  this.length = 0;

};

/**
 * @method addListElement
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Add a list element object to the list of list items 
 *
 * @param {ListElement} list_element - list element object to add to the list of list elements
 *
 * @return {boolean} indicating success or failure
 */

OpenAjax.a11y.cache.ContainerElement.prototype.addListElement = function (list_element) {

  if (list_element) {
    this.length += 1;
    list_element.document_order = this.length;
    list_element.cache_id = this.cache_id + "_li_" + this.length;
    this.list_elements.push(list_element);
    return true;
  }

  return false;

};


/**
 * @method addChildElement
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Add a list element object to the tree of list cache items 
 *
 * @param {ContainerElement | ListElement | LandmarkElement } list_element - list cache element object to add to the list cache
 *
 * @return {boolean} indicating success or failure
 */

OpenAjax.a11y.cache.ContainerElement.prototype.addChildElement = function (list_element) {

  if (list_element) {
    this.child_cache_elements.push(list_element);
    return true;
  }

  return false;

};

/**
 * @method isListOfLinks
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Check whether a list container contains at least the
 *       minimum number of li elements with one and only one link.
 *
 * @param {Number}  min_li  The minimum number of list elements with one link
 *                          that the list element must contain.
 *
 * @return {boolean} Returns true if the list is considered a list of links, otherwise false
 */

OpenAjax.a11y.cache.ContainerElement.prototype.isListOfLinks = function (min_li) {
 
  var child_elements = this.child_cache_elements;
  var max = child_elements.length;
  var i;  // loop counter
  var ce; // loop placeholder

  // results
  var count_li = 0;
  var count_li_with_link = 0;

  for (i = 0; i < max; i++) {
    ce = child_elements[i];

    // ignore elements that are not 'li'
    if (ce.list_type !== OpenAjax.a11y.LIST.ITEM) continue;

    // we've got an 'li' element
    count_li += 1;

    // but each must have a link_count of 1
    if (ce.link_count != 1) return false;
    count_li_with_link += 1;
  }

  return (count_li == count_li_with_link) && (count_li >= min_li);

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.ContainerElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.ContainerElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.ContainerElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.ContainerElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);
  
  cache_nls.addPropertyIfDefined(properties, this, 'list_type');
  cache_nls.addPropertyIfDefined(properties, this, 'link_count');
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.ContainerElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**

 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.ContainerElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.ContainerElement
 *
 * @desc Returns a text string representation of the container element object 
 *
 * @return {String} Returns string represention the container element object
 */

OpenAjax.a11y.cache.ContainerElement.prototype.toString = function () {

 return "List Container " + this.document_order + " with " + this.child_cache_elements.length + " list items"; 
 
};



/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                      OpenAjax Media Cache                        */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor MediaInfo
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a MediaInfo object for preserving the current media information 
 *        when traversing the DOM for audio and video information
 *
 * @param {MediaInfo} media_info - Current MediaInfo object
 *
 * @property {MediaElement}   media_element  - Parent MediaElement (if any)
 */

OpenAjax.a11y.cache.MediaInfo = function (media_info) {
 
 if (media_info) {
  this.media_element  = media_info.media_element;
 }
 else {
  this.media_element  = null;
 } 
}; 



/**
 * @constructor MediaCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates cache object representing information related to audio, video and other media objects in a document
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {Array}    media_elements  - List of media element objects in the document 
 * @property {Number}   length          - Number of media element objects in the list 
 *
 * @property {String}   sort_property   - Image element object property the list of media objects is sorted by
 * @property {Boolean}  sort_ascending  - true if list is sorted in ascending order, otherwise false 
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache
 */

OpenAjax.a11y.cache.MediaCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  this.up_to_date = false;
  
  this.media_elements = [];
  this.object_elements = [];
  this.video_elements = [];
  this.audio_elements = [];
  this.length = 0;
 
  this.sort_property = 'document_order';
  this.sort_ascending = false;
 
}; 

/**
 * @method addMediaElement
 * 
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Adds a media element object to the list of media elements and generates a cache id for the object.
 *
 * @param  {MediaElement}  media_element  - media element object to add 
 *
 * @return {Number} Returns the length of the list of media element objects
 */

OpenAjax.a11y.cache.MediaCache.prototype.addMediaElement = function ( media_element ) {

  // item must exist and have the position property
  if (media_element) {
    this.length = this.length + 1;
    media_element.cache_id = "media_" + this.length; 
    media_element.document_order = this.length;
    this.media_elements.push( media_element );
    
    if (media_element.dom_element.tag_name === 'video')  this.video_elements.push(media_element);
    if (media_element.dom_element.tag_name === 'audio')  this.audio_elements.push(media_element);
    if (media_element.dom_element.tag_name === 'object') this.object_elements.push(media_element);

 } 

 return this.length;

};

/**
 * @method getMediaElementByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Finds the the media element object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of media element object
 *
 * @return {MediaElement | null} Returns cache media element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.MediaCache.prototype.getMediaElementByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Finds the the media element object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of media element object
 *
 * @return {MediaElement | null} Returns cache media element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.MediaCache.prototype.getItemByCacheId = function (cache_id) {

  var i;
  var media_elements_len = this.media_elements.length;

  if (cache_id) {  
    for (i=0; i < media_elements_len; i++) {
      if (this.media_elements[i].cache_id == cache_id) {
        return this.media_elements[i];
      }
    } // end loop
  } 
  return null;
};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Resests the media cache object properties and empties all the lists and arrays 
 */

OpenAjax.a11y.cache.MediaCache.prototype.emptyCache = function () {

  this.media_elements  = [];
  this.audio_elements  = [];
  this.video_elements  = [];
  this.object_elements = [];
  
  this.sort_property = 'document_order';
  this.sort_ascending = false;
  this.up_to_date = false;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Updates the media cache by checking to see if a dom element
 *          should be added to the cache
 *  
 * @param  {DOMElement}   dom_element   - dom element object to check for inclusion in media cache
 * @param  {MediaInfo}    media_info  - Information about the current media element relationships in the DOM
 *
 */
 
OpenAjax.a11y.cache.MediaCache.prototype.updateCacheItems = function (dom_element, media_info) {

  var mi = new OpenAjax.a11y.cache.MediaInfo(media_info);
  var media_element;

  if ((dom_element.tag_name === 'object') ||
      (dom_element.tag_name === 'applet') ||
      (dom_element.tag_name === 'embed') ||
      (dom_element.tag_name === 'audio') ||
      (dom_element.tag_name === 'video')) {

    media_element = new OpenAjax.a11y.cache.MediaElement(dom_element);    
    this.dom_cache.media_cache.addMediaElement(media_element);
    
    mi.media_element = media_element;
    
  }
  else {
  
    if ((dom_element.tag_name === 'param') &&
        (media_info.media_element && media_info.media_element.dom_element.tag_name === 'object')) {
       media_element = new OpenAjax.a11y.cache.MediaChildElement(dom_element);    
       media_info.media_element.addMediaElement(media_element);          
    }    
    
    if ((dom_element.tag_name === 'track') &&
        (media_info.media_element && 
         (media_info.media_element.dom_element.tag_name === 'video') || 
         (media_info.media_element.dom_element.tag_name === 'audio'))) {
       media_element = new OpenAjax.a11y.cache.MediaChildElement(dom_element);    
       media_info.media_element.addMediaElement(media_element);          
    }    

  }
  
  return mi;
    
};

/**
 * @method traverseDOMElementsForMediaElements
 *
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Traverses DOMElement objects in the tree to update the media cache 
 *
 * @param  {DOMElement}  dom_element - dom element object to check for inclusion in media cache
 * @param  {MediaInfo}   media_info  - information about a media elements
 */
 
OpenAjax.a11y.cache.MediaCache.prototype.traverseDOMElementsForMediaElements = function (dom_element, media_info) {

  var i;

  if (!dom_element) return;

  if (dom_element.type == Node.ELEMENT_NODE) {

    var mi = this.updateCacheItems(dom_element, media_info);
  
    for (i=0; i<dom_element.child_dom_elements.length; i++) {
      this.traverseDOMElementsForMediaElements(dom_element.child_dom_elements[i], mi);
    } // end loop
  }  
  
};


/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Traverses the DOMElements to update the media cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the media cache are disabled, this cache would 
 *       not be updated
 */
 
OpenAjax.a11y.cache.MediaCache.prototype.updateCache = function () {
  var i;
  var children = this.dom_cache.element_cache.child_dom_elements;
  var children_len = children.length;
 
  var media_info = new OpenAjax.a11y.cache.MediaInfo();
 
  for (i=0; i < children_len; i++) {
    this.traverseDOMElementsForMediaElements(children[i], media_info);
  }  

  this.up_to_date = true;
};

/**
 * @method sortMediaElements
 *
 * @memberOf OpenAjax.a11y.cache.MediaCache
 *
 * @desc Sorts media element array by a media element object property
 *
 * @param {String}   property   - Property of media element object to sort the list
 * @param {Boolean}  ascending  - true if sort in ascending order; false in descending order
 *
 * @return {Boolean}  Returns true if list was sorted, false if not
 */

OpenAjax.a11y.cache.MediaCache.prototype.sortMediaElements = function(property, ascending ) {

  var swapped = false;
  var temp = null;
  var i;

  if (this.media_elements && 
      this.media_elements.length && 
      !this.media_elements[0][property] ) {
    return false;
  } // endif

  var media_elements_len = this.media_elements.length;

  if (ascending) {
    do {
      swapped = false;
      for (i=1; i<media_elements_len; i++) {
        if (this.media_elements[i-1][property] > this.media_elements[i][property]) {
          // swap the values
          temp = this.media_elements[i-1];
          this.media_elements[i-1] = this.media_elements[i];
          this.media_elements[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  }
  else {
    do {
      swapped = false;
      for (i = 1; i < media_elements_len; i++) {
        if (this.media_elements[i-1][property] < this.media_elements[i][property]) {
          // swap the values
          temp = this.media_elements[i-1];
          this.media_elements[i-1] = this.media_elements[i];
          this.media_elements[i] = temp;
          swapped = true;
        } 
      } // end loop
    } while (swapped);
  } 

  this.sort_property = property;

  return true;

};


/* ---------------------------------------------------------------- */
/*                            MediaElement                          */
/* ---------------------------------------------------------------- */

/**
 * @constructor MediaElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates media element object representing information related to an object, video, audio, embed or applet element on a web page
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the media element 
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the media element
 * @property  {String}      cache_id        - String that uniquely identifies the media element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the media element in the document in relationship to other media elements
 *
 * @property  {Number}  is_video               - Constant indicating the probability of the media element including video
 * @property  {Number}  is_audio               - Constant indicating the probability of the media element including audio
 * @property  {Number}  has_caption            - Constant indicating the probability of the media element having a caption 
 * @property  {Number}  has_text_alternative   - Constant indicating the probability of the media element having a text description
 * @property  {Number}  has_audio_description  - Constant indicating the probability of the media element having an audio description
 */

OpenAjax.a11y.cache.MediaElement = function (dom_element) {

  this.document_order = 0;
 
  this.dom_element = dom_element;
  
  this.child_cache_elements = [];
  
  this.length = 0;
  
  this.type       = dom_element.node.getAttribute('type');
  this.src        = dom_element.node.getAttribute('src');
  this.data       = dom_element.node.getAttribute('data');
  this.alt        = dom_element.node.getAttribute('alt');
  this.longdesc   = dom_element.node.getAttribute('longdesc');
  this.name       = dom_element.node.getAttribute('name');
  this.height     = dom_element.node.getAttribute('height');
  this.width      = dom_element.node.getAttribute('width');
  
  this.is_live                        = OpenAjax.a11y.MEDIA.MAYBE;
  this.is_video                       = OpenAjax.a11y.MEDIA.MAYBE;
  this.is_audio                       = OpenAjax.a11y.MEDIA.MAYBE;
  this.has_caption                    = OpenAjax.a11y.MEDIA.MAYBE; 
  this.has_text_alternative           = OpenAjax.a11y.MEDIA.MAYBE; 
  this.has_audio_description          = OpenAjax.a11y.MEDIA.MAYBE;
  this.has_extended_audio_description = OpenAjax.a11y.MEDIA.MAYBE;
  this.has_sign_language              = OpenAjax.a11y.MEDIA.MAYBE;

  switch (dom_element.tag_name) {
  case 'video':
    this.is_video = OpenAjax.a11y.MEDIA.YES;
    this.is_audio = OpenAjax.a11y.MEDIA.MAYBE;
    break;
    
  case 'audio':
    this.is_video = OpenAjax.a11y.MEDIA.NO;
    this.is_audio = OpenAjax.a11y.MEDIA.YES;
    break;
  
  default:
    break;
  }
  
};

/**
 * @method addMediaElement
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 * 
 * @desc Adds a cache media element to the tree representation of media elements
 *
 * @param  {MediaElement } media_element   - Cache media element object to add 
 */

OpenAjax.a11y.cache.MediaElement.prototype.addMediaElement = function (media_element) {

 if (media_element) {
    this.length = this.length + 1;
    media_element.cache_id = this.cache_id + "_child_" + this.length; 
    media_element.document_order = this.length;    
    this.child_cache_elements.push(media_element); 
 }  

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.MediaElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.MediaElement.prototype.getStyle = function () {

  return  this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.MediaElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var attributes = this.dom_element.getAttributes(unsorted);
  
  cache_nls.addPropertyIfDefined(attributes, this, 'name');
  cache_nls.addPropertyIfDefined(attributes, this, 'type');
  cache_nls.addPropertyIfDefined(attributes, this, 'src');
  cache_nls.addPropertyIfDefined(attributes, this, 'data');
  cache_nls.addPropertyIfDefined(attributes, this, 'alt');
  cache_nls.addPropertyIfDefined(attributes, this, 'longdesc');
  cache_nls.addPropertyIfDefined(attributes, this, 'height');
  cache_nls.addPropertyIfDefined(attributes, this, 'width');

  return attributes;
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.MediaElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
  
  cache_nls.addPropertyIfDefined(properties, this, 'is_live');
  cache_nls.addPropertyIfDefined(properties, this, 'is_video');
  cache_nls.addPropertyIfDefined(properties, this, 'is_audio');
  cache_nls.addPropertyIfDefined(properties, this, 'has_caption');
  cache_nls.addPropertyIfDefined(properties, this, 'has_text_alternative');
  cache_nls.addPropertyIfDefined(properties, this, 'has_audio_description');
  cache_nls.addPropertyIfDefined(properties, this, 'alt_for_comparison');
  cache_nls.addPropertyIfDefined(properties, this, 'document_order');
  
  return properties;
  
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.MediaElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.MediaElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.MediaElement
 *
 * @desc Creates a text string representation of the media element object 
 *
 * @return {String} Returns a text string representation of the media element object
 */
 
 OpenAjax.a11y.cache.MediaElement.prototype.toString = function () {
   return this.dom_element.tag_name;
 };


/* ---------------------------------------------------------------- */
/*                            MediaChildElement                          */
/* ---------------------------------------------------------------- */

/**
 * @constructor MediaChildElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates media child element object representing possible caption and audio description information related to an object, video, audio on a web page
 *
 * @param  {DOMelement}   dom_element   - The dom element object representing the media element 
 *
 * @property  {DOMElement}  dom_element     - Reference to the dom element representing the media element
 * @property  {String}      cache_id        - String that uniquely identifies the media element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the media element in the document in relationship to other media elements
 */

OpenAjax.a11y.cache.MediaChildElement = function (dom_element) {

  this.document_order = 0;
  this.cache_id = "";
 
  this.dom_element = dom_element;

  this.name         = dom_element.node.getAttribute('name');
  this.value        = dom_element.node.getAttribute('value');
  this.src          = dom_element.node.getAttribute('src');
  this.kind         = dom_element.node.getAttribute('kind');
  this.srclang      = dom_element.node.getAttribute('srclang');
  this.label        = dom_element.node.getAttribute('label');
  this.default_attr = dom_element.node.getAttribute('default');

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.MediaChildElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.MediaChildElement.prototype.getStyle = function () {

  return  this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.MediaChildElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var attributes = [];
  
  cache_nls.addPropertyIfDefined(attributes, this, 'name');
  cache_nls.addPropertyIfDefined(attributes, this, 'value');
  cache_nls.addPropertyIfDefined(attributes, this, 'src');
  cache_nls.addPropertyIfDefined(attributes, this, 'kind');
  cache_nls.addPropertyIfDefined(attributes, this, 'srclang');
  cache_nls.addPropertyIfDefined(attributes, this, 'label');
  cache_nls.addPropertyIfDefined(attributes, this, 'default_attr');
   
  return attributes;
   
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.MediaChildElement.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = [];
    
  return properties;
  
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.MediaChildElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.MediaChildElement.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.MediaChildElement
 *
 * @desc Creates a text string representation of the media element object 
 *
 * @return {String} Returns a text string representation of the media element object
 */
 
 OpenAjax.a11y.cache.MediaChildElement.prototype.toString = function () {
   return this.dom_element.tag_name;
 };


/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

 
/**
 * @constructor DOMElementComputedStyle
 *
 * @memberOf OpenAjax.a11y.cache
 * 
 * @desc Create a dom element computed style object is used to add style properties to dom element cache objects 
 * 
 * @param  {DOMElement}  dom_element     - dom element node to add computed style information to 
 * @param  {DOMElement}  parent_element  - parent dom element node for computing inherited properties 
 *
 * @property  {String}  display     - Computed value of the CSS 'display' property
 * @property  {String}  visibility  - Computed value of the CSS 'visibility' property
 *
 * @property  {Number}  is_visible_onscreen   - Constant representing the graphical visibility of the element (i.e is it visible to people with sight)
 * @property  {Number}  is_visible_to_at      - Constant representing the assistive technology visibility of the element (i.e is it visible to people using a screen reader)
 * 
 * @property  {String}  color                 - Computed value of the CSS 'color' property
 * @property  {String}  color_hex             - Computed value of the CSS 'color' property in hexidecimal format
 * @property  {String}  opacity               - Computed value of the CSS 'opacity' property
 * @property  {String}  background_color      - Computed value of the CSS 'background-color' property
 * @property  {String}  background_color_hex  - Computed value of the CSS 'background-color' property in hexidecimal format
 * @property  {String}  background_image      - Computed value of the CSS 'background-image' property
 * @property  {String}  background_repeat     - Computed value of the CSS 'background-repeat' property
 * @property  {String}  background_position   - Computed value of the CSS 'background-position' property
 *
 * @property  {String}  font_family  - Computed value of the CSS 'font-family' property
 * @property  {String}  font_size    - Computed value of the CSS 'font-size' property
 * @property  {String}  font_weight  - Computed value of the CSS 'font-weight' property
 *
 * @property  {String}  position  - Computed value of the CSS 'position' property
 * @property  {String}  left      - Computed value of the CSS 'left' property  
 * @property  {String}  top       - Computed value of the CSS 'top' property
 * @property  {String}  width     - Computed value of the width of the rendered element in pixels  
 * @property  {String}  height    - Computed value of the height of the rendered element in pixels
 */
 
OpenAjax.a11y.cache.DOMElementComputedStyle = function (dom_element, parent_element) {

  function normalizeBackgroundImage(value, parent_element) {

    var v = value;

    if ((value.toLowerCase() === 'inherit') || 
        (value.toLowerCase() === 'none') || 
        (value === '')) {
    
      if (parent_element) {  
        v = parent_element.computed_style.background_image;
      }
      else {
        v = 'none';
      }
    }  
  
    return v;
  
  } // end function

  function  normalizeFontSize(value, parent_element) {
    if (value.toLowerCase() == 'inherit') {
      if (parent_element) {
        return parent_element.computed_style.font_size;
      }
      else {
        return 12;
      }    
    }
    else {
      return value;
    }
  } // end function

  function  normalizeFontWeight(value, parent_element) {
    if (isNaN(value) ) {
      switch (value.toLowerCase()) {
      case 'bold':
        return 700;

      case 'normal':
        return 400;

      case 'inherit':
        if (parent_element) {
          return parent_element.computed_style.font_weight;
        }
        else {
          return 400;
        }    

      case 'bolder':
        return 700;
    
      default:
        return 400;
      }
    }
    else {
      return parseInt(value,10);
    }
  } // end function


  function  normalizePositionTop(value, parent_element) {
    if (value.toLowerCase() == 'inherit') {
      if (parent_element) {
        return parent_element.computed_style.top;
      }
      else {
        return 0;
      }    
    }
    else {
      return parseInt(value,10);
    }
  } // end function

  function  normalizePositionLeft(value, parent_element) {
    if (value.toLowerCase() == 'inherit') {
      if (parent_element) {
        return parent_element.computed_style.left;
      }
      else {
        return 0;
      }    
    }
    else {
      return parseInt(value,10);
    }
  } // end function

  this.display  = "";
  this.visibility = "";

  this.is_visible_onscreen = OpenAjax.a11y.VISIBILITY.UNKNOWN;  
  this.is_visible_to_at = OpenAjax.a11y.VISIBILITY.UNKNOWN;

  this.color   = "";
  this.background_color = "";
  this.background_image = "";
  this.font_family = "";
  this.font_size  = "";
  this.font_weight = "";
  this.position  = "";
  this.left    = "";
  this.top     = "";
 
  // check to see if getComputedStyle is defined for the engine 
  if (!window.getComputedStyle) return;

//  OpenAjax.a11y.logger.info("Element: " + dom_element );  

  var style = window.getComputedStyle(dom_element.node, null);  
   
  this.display    = style.getPropertyValue("display");
  this.visibility = style.getPropertyValue("visibility");
 
  this.color               = style.getPropertyValue("color");
  this.opacity             = style.getPropertyValue("opacity");
  this.background_color    = style.getPropertyValue("background-color");
  this.background_image    = normalizeBackgroundImage(style.getPropertyValue("background-image"), parent_element);
  this.background_repeat   = style.getPropertyValue("background-repeat");
  this.background_position = style.getPropertyValue("background-position");
 
  this.font_family = style.getPropertyValue("font-family");  
  this.font_size   = normalizeFontSize(style.getPropertyValue("font-size"), parent_element); 
  this.font_weight = normalizeFontWeight(style.getPropertyValue("font-weight"), parent_element); 
 
  this.position = style.getPropertyValue("position");

  // test if getBoundingClientRect is supported 
  if (dom_element.node.getBoundingClientRect) {
    var client_rect = dom_element.node.getBoundingClientRect();
    this.client_rect = client_rect;
    this.top     = client_rect.top;
    this.left    = client_rect.left;
    this.height  = client_rect.height;
    this.width   = client_rect.width;  
  }
  else {
    this.top     = normalizePositionTop(style.getPropertyValue("top"), parent_element);
    this.left    = normalizePositionLeft(style.getPropertyValue("left"), parent_element);
  }
 
  // This is an edge case test typcially for body elements and frames
  if ((this.background_color == 'inherit') ||
      (this.background_color == 'transparent')) {
    if (parent_element && parent_element.computed_style) { 
      this.background_color   = parent_element.computed_style.background_color;
      this.background_color_hex = parent_element.computed_style.background_color_hex;
    }
    else {
      this.background_color = 'rgb(255,255,255)';
      this.background_color_hex = 'ffffff';
    }   
  } 
  else {
    this.background_color_hex = OpenAjax.a11y.util.RGBToHEX(style.getPropertyValue("background-color")); 
  }

  if (parent_element && 
      parent_element.computed_style ) {

    var parent_style = parent_element.computed_style;

    // We do have parent_element so use its information if needed  
 
    if ((this.display === 'inherit') ||  
        (parent_element.computed_style.display == 'none')) {
      this.display = 'none';
    } 

    if ((this.visibility === 'inherit') ||
        (parent_style.visibility === 'hidden')) {
      this.visibility = parent_style.visibility;
    } 

    if (this.color == 'inherit') {
      this.color = parent_style.color;
      this.color_hex = parent_style.color_hex;
    }
    else {
      this.color_hex = OpenAjax.a11y.util.RGBToHEX(style.getPropertyValue("color"));
    }
    
    if (this.font_family === 'inherit') {
      this.font_family = parent_style.font_family;
    } 
  
    if (this.position === 'inherit') {
      this.position = parent_style.position;
    } 
  } 
 
  // Calcuate visibility of node content in graphical renderings and to assistive technologies

  if (this.visibility && 
      this.visibility.length && 
      this.display && 
      this.display.length ) { 
      
    if ((this.visibility === 'hidden') ||
        (this.display === 'none')) {
        
      if (dom_element.tag_name !== 'area') {
        this.is_visible_onscreen = OpenAjax.a11y.VISIBILITY.HIDDEN;    
        this.is_visible_to_at    = OpenAjax.a11y.VISIBILITY.HIDDEN;
      }
      else {
        this.is_visible_onscreen = OpenAjax.a11y.VISIBILITY.VISIBLE;    
        this.is_visible_to_at    = OpenAjax.a11y.VISIBILITY.VISIBLE;     
      }
      
    } 
    else {
      if (this.position === "absolute" &&
          (parseInt(this.top,10) < 0 || parseInt(this.left,10) < 0)) {
        this.is_visible_onscreen = OpenAjax.a11y.VISIBILITY.HIDDEN;
      }
      else {
        this.is_visible_onscreen = OpenAjax.a11y.VISIBILITY.VISIBLE;
      }
   
      if ((typeof dom_element.aria_hidden === 'string') && 
          (dom_element.aria_hidden === "true")) {
        this.is_visible_to_at = OpenAjax.a11y.VISIBILITY.HIDDEN;  
      } 
      else {
        this.is_visible_to_at = OpenAjax.a11y.VISIBILITY.VISIBLE;     
      }
    }
  } 

  this.is_large_font = (parseInt(this.font_size,10) >= 18) || ((parseInt(this.font_size,10) >= 14) && (parseInt(this.font_weight,10) >= 300));

};

/**
 * @method calculateColorContrast
 *  
 * @memberOf OpenAjax.a11y.cache.DOMElementComputedStyle
 * 
 * @desc Calculates a color contrast raio (CCR) value for the element style object 
 *
 * @return {Number}  Returns a number representing the color contrast ratio (CCR)
 */ 

OpenAjax.a11y.cache.DOMElementComputedStyle.prototype.calculateColorContrastRatio = function () {

 if( this.color_hex && 
   (this.color_hex.length == 6) && 
    this.background_color_hex && 
   (this.background_color_hex.length == 6)) {
  var L1 = this.getLuminance(this.color_hex);
  var L2 = this.getLuminance(this.background_color_hex);
  this.color_contrast_ratio = Math.round((Math.max(L1, L2) + 0.05)/(Math.min(L1, L2) + 0.05)*10)/10;
 }
 else {
  this.color_contrast_ratio = null;
 }

 return this.color_contrast_ratio;
   
};


/**
 * @method getLuminance
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementComputedStyle
 * 
 * @desc Get the luminance value of a hex incoded color 
 *
 * @param {String}  color  - Hex representation of a CSS color value
 * 
 * @return {Number}  Returns a number representing the limnance value
 */ 

OpenAjax.a11y.cache.DOMElementComputedStyle.prototype.getLuminance = function (color) {

 // OpenAjax.a11y.logger.debug("  " + color );

 // Get decimal values
 var R8bit = parseInt(color.substring(0,2),16);
 var G8bit = parseInt(color.substring(2,4),16);
 var B8bit = parseInt(color.substring(4,6),16);
        
 // Get sRGB values
 var RsRGB = R8bit/255;
 var GsRGB = G8bit/255;
 var BsRGB = B8bit/255;
  // Calculate luminance
 var R = (RsRGB <= 0.03928) ? RsRGB/12.92 : Math.pow(((RsRGB + 0.055)/1.055), 2.4);
 var G = (GsRGB <= 0.03928) ? GsRGB/12.92 : Math.pow(((GsRGB + 0.055)/1.055), 2.4);
 var B = (BsRGB <= 0.03928) ? BsRGB/12.92 : Math.pow(((BsRGB + 0.055)/1.055), 2.4);
			
 return (0.2126 * R + 0.7152 * G + 0.0722 * B);
			
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.DOMElementComputedStyle
 *
 * @desc Creates a text string representation of the computed style object 
 *
 * @return {String} Returns a text string representation of the computed style object
 */ 

OpenAjax.a11y.cache.DOMElementComputedStyle.prototype.toString = function (color) {
  return "Computed style " + this.color_hex + " " + this.background_color_hex + " " + this.color_contrast_ratio; 
};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                            TableInfo                             */
/* ---------------------------------------------------------------- */

/**
 * @constructor TableInfo
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a TableInfo object for preserving the current table information 
 *        when traversing the DOM for table information
 *
 * @property {table cache object}   parent_element     - Parent table cache Object (if any)
 * @property {TableElement}         table_element      - Parent TableElement (if any)
 * @property {TBodyElement}         table_body_element - Parent TBodyElement (if any)
 * @property {TableRowElement}      table_row_element  - Parent TableRowElement (if any)
 * 
 * @param {TableInfo} table_info - Current ControlInfo object
 */
 
 OpenAjax.a11y.cache.TableInfo = function (table_info) {

   if (table_info) {
     this.parent_element      = table_info.parent_element;
     this.table_element       = table_info.table_element;
     this.table_body_element  = table_info.table_body_element;
     this.table_row_element   = table_info.table_row_element;
   }
   else {
     this.parent_element      = null;
     this.table_element       = null;
     this.table_body_element  = null;
     this.table_row_element   = null;
   }  
 }; 
 
/* ---------------------------------------------------------------- */
/*                          TablesCache Object                      */
/* ---------------------------------------------------------------- */

/** 
 * @constructor TablesCache
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Create a table cache object to hold information about tables in a web page
 *          
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache}  dom_cache   - Reference to the DOMCache object 
 * @property {Boolean}   up_to_date  - true if the cache has been creating using the current DOMElements, else false
 *                                       NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                             based on whether a rule needs the cache
 *
 * @property {Array}    child_cache_elements - Root array of the tree representation of the table elements in the document 
 *
 * @property {Array}    table_elements - Array of all the TableElement objects in the cache  
 * @property {Number}   length         - Running length of the table_elements array for use in calculating cache_id values
 * 
 * @property {Array}    rule_results   - Root array of the tree representation of the table elements in the document 
 *
 * @property {ResultRuleSummary}  rule_summary_result  - Rule results associated with this cache 
 */
OpenAjax.a11y.cache.TablesCache = function (dom_cache) {

  // Private properties
  this.dom_cache = dom_cache;
  this.up_to_date = false;

  // Public properties
  this.child_cache_elements = [];    
   
  this.table_elements = [];  
  this.length         = 0;  

  this.page_element  = null;  


};

/**
 * @method addTableElement
 * 
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Adds a table element object to the list of tables and generates a cache_id for the table element object
 *
 * @param {TableElement}  table_element   - TableElement object to add to the cache
 *
 * @return {Number} Returns the number of table element objects in the list
 *
 */
 OpenAjax.a11y.cache.TablesCache.prototype.addTableElement = function (table_element) {

   // item must exist and have the position property
   if (table_element) {
     this.length = this.length + 1;
     table_element.document_order = this.length;
     table_element.cache_id = "table_" + this.length;
     this.table_elements.push( table_element );
   } 

   return this.length;

 };

/** 
 * @method addChild
 * 
 * @memberOf OpenAjax.a11y.cache.TablesCache
 * 
 * @desc Adds a cache table element to the tree representation of the table in the table cache
 *
 * @param  {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement }  table_element  - Cache table element object to add to root of tree of table elements 
 */
 
 OpenAjax.a11y.cache.TablesCache.prototype.addChild = function (table_element) {

   if (table_element) {
     this.child_cache_elements.push(table_element); 
   }  
 }; 

/** 
 * @method addRuleResult
 * 
 * @memberOf OpenAjax.a11y.cache.TablesCache
 * 
 * @desc Add a RuleResult reference to the table cache 
 *
 * @param {RuleResult}  rule_result - Rule result to associate with the table cache 
 */
 OpenAjax.a11y.cache.TablesCache.prototype.addRuleResult = function (rule_result) {

   if (rule_result) {
     this.rule_results.push(rule_result); 
   }  
 }; 

/**
 * @deprecated getTableElementByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Finds the the table cache element object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of table cache element object
 *
 * @return {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement | null} Returns cache table element object if cache id is found, otherwise null
 */
 OpenAjax.a11y.cache.TablesCache.prototype.getTableElementByCacheId = function (cache_id) {
   return this.getItemByCacheId(cache_id);
 };

/**
 * @method getItemByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Finds the the table cache element object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of table cache element object
 *
 * @return {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement | null} Returns cache table element object if cache id is found, otherwise null
 */
 OpenAjax.a11y.cache.TablesCache.prototype.getItemByCacheId = function (cache_id) {

   var i;
   var te;
   var table_elements_len = this.table_elements.length;
   var id_info = cache_id.split('_');
   var table_id = "table_" + id_info[1];
   
   for (i = 0; i < table_elements_len; i++) {
     te = this.table_elements[i];

     if (te.cache_id == cache_id) {
       return te;
     }
     else {
       if (te.cache_id == table_id) {
         return te.getTableElementByCacheId(cache_id);
       }
     }
   }
     
   return null;
 };

/**
 * @method getRuleResultByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Finds the the rule result object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of table cache element object
 *
 * @return {ResultRule | null} Returns cache rule result object if cache id is found, otherwise null
 */
 OpenAjax.a11y.cache.TablesCache.prototype.getRuleResultByCacheId = function (cache_id) {

   var i;
   var rr;
   var rule_results     = this.evaluation_results.rule_results;
   var rule_results_len = rule_results.length;
      
   for (i = 0; i < rule_results_len; i++) {
     rr = rule_results[i];
     if (rr.cache_id == cache_id) return rr;
   } // end loop
     
   return null;
 };
/**
 * @method getRuleResultByRuleId
 *
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Gets the rule result object with the matching rule id
 *
 * @param {String} rule_id - rule id of table element
 *
 * @return {RuleResult | null}}  Returns rule result object if rule id is found, otherwise null
 */
 
 OpenAjax.a11y.cache.TablesCache.prototype.getRuleResultByRuleId = function (rule_id) {

  var i;
  var rr;
  var rule_results_len = this.rule_results.length;
   
  for (i = 0; i < rule_results_len; i++) {
    rr = this.rule_results[i];

    if (rr.rule.rule_id == rule_id) {
      return rr;
    }
  }
     
  return null;
};


/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Updates the tables cache object by checking to see if a dom element object
 *          should be added to the table cache objects
 *  
 * @param  {DOMElement}  dom_element  - DOMElement object to check for inclusion in tables cache
 * @param  {TableInfo}   table_info   - Information about the current table relationships in the DOM
 */
 
 OpenAjax.a11y.cache.TablesCache.prototype.updateCacheItems = function (dom_element, table_info) {

   var te;
   var tce;
   var ce;
   var tbe;
   var the;
   var tre;
   
   var ti = new OpenAjax.a11y.cache.TableInfo(table_info);

   switch (dom_element.tag_name) {

     case 'table':
       te = new OpenAjax.a11y.cache.TableElement(this.dom_cache, dom_element, table_info);
       this.addTableElement(te);
  
       if (table_info.parent_element) {
         table_info.parent_element.addChild(te);   
       }
       else {
         this.addChild(te);
       }
       
       ti.parent_element = te;   
       ti.table_element  = te;    
       ti.table_body_element  = null;    
       ti.table_row_element  = null;    
             
       break;

     case 'caption':
       ce = new OpenAjax.a11y.cache.CaptionElement(dom_element, table_info);

       if (table_info.table_element) {
         table_info.table_element.addTableElement(ce);   
         if (table_info.parent_element) {
           table_info.parent_element.addChild(ce);
         }
       }
 
       break;

     case 'thead':
       the = new OpenAjax.a11y.cache.THeadElement(dom_element, table_info);

       if (table_info.table_element) {
         table_info.table_element.addTableElement(the);   
         if (table_info.parent_element) {
           table_info.parent_element.addChild(the);   
         } 
       }

       ti.parent_element     = the;   
       ti.table_body_element = the;   
       ti.table_row_element  = null;    

       break;

     case 'tbody':
       tbe = new OpenAjax.a11y.cache.TBodyElement(dom_element, table_info);

       if (table_info.table_element) {
         table_info.table_element.addTableElement(tbe);   
         
         if (table_info.parent_element) {
           table_info.parent_element.addChild(tbe);   
         }
       }
       
       ti.parent_element = tbe;   
       ti.table_body_element = tbe;   
       ti.table_row_element  = null;    

       break;

     case 'tr':
       tre = new OpenAjax.a11y.cache.TableRowElement(dom_element, table_info);

       if (table_info.table_element) {
         table_info.table_element.addTableElement(tre);   
         
         if (table_info.parent_element) {
           table_info.parent_element.addChild(tre);   
         }
         
         if (table_info.table_body_element) {
           table_info.table_body_element.row_count++;
         }
       }
 
       ti.parent_element     = tre;
       ti.table_row_element  = tre;

       break;


     case 'td':
     case 'th':
       tce = new OpenAjax.a11y.cache.TableCellElement(dom_element, table_info);
       
       if (table_info.table_element) {
         table_info.table_element.addTableElement(tce);   
                  
         if (table_info.parent_element) {
           table_info.parent_element.addChild(tce);   
         }
       }
 
       ti.parent_element      = tce;
       
       break;
       
     case 'body':
       if (!this.page_element) {  
         this.page_element = new OpenAjax.a11y.cache.PageElementLayout(dom_element);
       } 
       break;
           
     default:
       break;

   } // end switch

   return ti;
 };

/**
 * @method traverseDOMElementsForTableElements
 *
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Traverses the DOMElements to update table elements
 *
 * @param {TableElement}      dom_element  - DOMElement object to check fo inclusion in tables cache
 * @param {TableInformation}  table_info   - Information needed for identifying the parent/child relationships of nested tables
 */
 
 OpenAjax.a11y.cache.TablesCache.prototype.traverseDOMElementsForTableElements = function (dom_element, table_info) {

   var i;
   var ti;

   if (!dom_element) return;

     if (dom_element.type == Node.ELEMENT_NODE) {

       ti = this.updateCacheItems(dom_element, table_info);
  
       for (i=0; i<dom_element.child_dom_elements.length; i++ ) {
         this.traverseDOMElementsForTableElements(dom_element.child_dom_elements[i], ti);
       } // end loop
     }  
 }; 

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.TablesCache
 *
 * @desc Traverses the DOMElements to update the tables cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the controls cache are disabled, this cache would 
 *       not be updated
 */
 
 OpenAjax.a11y.cache.TablesCache.prototype.updateCache = function () {
   
   var i;
   var children = this.dom_cache.element_cache.child_dom_elements;
   var children_len = children.length;

   var table_info = new OpenAjax.a11y.cache.TableInfo(null);

 
   for (i=0; i < children_len; i++) {
     this.traverseDOMElementsForTableElements(children[i], table_info);
   }  
   
   this.up_to_date = true;
 };

/* ---------------------------------------------------------------- */
/*                       TableElement Object                        */
/* ---------------------------------------------------------------- */

/**
 * @constructs TableElement
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates table element object used to hold data about a table 
 *         
 * @param  {DOMCache}          dom_cache           - Reference to the current dom cache for use of dom cache methods to find references   
 * @param  {DOMElement}        dom_element         - dom_element object provides information about current dom node 
 * @param  {TableCellElement}  table_cell_element  - table_cell_element object provides information about the table 
 *                                                   cell the table may be a child of in the dom 
 *
 * @property  {DOMCache}    dom_cache    - DOMCache reference for reference DOMCache methods for calculating headers
 * @property  {DOMElement}  dom_element  - DOMElement associated with the form element
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 * @property  {Number}      document_order  - Ordinal position of the table element in the document in relationship to other table elements
 *
 * @property  {Array}       child_cache_elements  - Array of cache table elements as part of table elements relationship tree 
 * @property  {Array}       table_elements        - List of all table element objects in this table element object
 * @property  {Number}      length                - Number of table element objects 
 *
 * @property  {String}  effective_summary  - The calculated description of a data table, empty for layout tables
 * @property  {String}  effective_caption  - The calculated name of a data table, empty for layout tables
 *
 * @property  {Number}  max_row     - Number of rows in a table  
 * @property  {Number}  max_column  - Number of columns in a table
 * 
 * @property  {Number}  row     - Used as the current row counter when traversing a table dom elements  
 * @property  {Number}  column  - Used as the current column counter when traversing a table dom elements
 * 
 * @property  {Array}   cells     - A two dimensional array representing the table row and columns
 * @property  {Array}   cell_ids  - List of table cell objects who have an id attribute defined
 *
 * @property  {Boolean}  is_data_table          - True if the table is identified as a data table
 * @property  {Boolean}  is_complex_data_table  - True if the table is identified as a complex data table
 *
 * @return {TableElement}
 */
 OpenAjax.a11y.cache.TableElement = function (dom_cache, dom_element, table_info) {

   if( !dom_element ) return null;  

   this.type = OpenAjax.a11y.TABLE.TABLE_ELEMENT;

   this.dom_cache      = dom_cache;
   this.dom_element    = dom_element;
   this.cache_id       = "";
   this.document_order = 0;
   
   this.child_cache_elements = [];

   this.table_elements = [];
   this.length = 0;

   this.effective_caption = "";
   this.effective_caption_for_comparison = "";
   
   this.summary = this.dom_element.node.getAttribute("summary");

   this.effective_summary = this.summary;

   if (this.effective_summary && this.effective_summary.length) { 
     this.effective_summary_for_comparison = this.effective_summary.normalizeSpace().toLowerCase();
   }
   else {
     if (dom_element.aria_describedby) {
       this.effective_summary = dom_cache.getTextFromIDs(dom_element.aria_describedby);
       this.effective_summary_for_comparison = this.effective_summary.normalizeSpace().toLowerCase();
     }
     else {
       this.effective_summary = "";   
       this.effective_summary_for_comparison = "";   
     }  
   }
 
   this.max_row = 0; 
   this.max_column = 0;

   this.cell_count = 0;
   
   this.cell_ids = [];

   this.row      = -1;   
   this.column   = 0;  
   
   this.cells    = [];
   this.cells[0]  = [];
   this.cells[0][0] = null;
 
   this.is_data_table = false;
   this.is_complex_data_table = false;

   this.nesting_level        = 0;
   this.layout_nesting_level = 0;
   this.layout_table_in_data_table = false;
   this.data_table_in_data_table = false;

   this.parent_table_element = table_info.table_element;
   
   if (table_info.table_element) {
     
     this.nesting_level = table_info.table_element.nesting_level + 1;
     
     if (table_info.table_element.is_data_table) {
       this.layout_in_data_table = true; 
     }
     
     if (table_info.table_element.max_column > 1) {
       this.layout_nesting_level = table_info.table_element.layout_nesting_level + 1;   
     }
     
   }
 
   return this;
 };

/**
 * @method setIsDataTable
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc  Set is a data table property, if not already set   
 */
 
OpenAjax.a11y.cache.TableElement.prototype.setIsDataTable = function () {

  // if role=presentation this is a layout table
  if (this.dom_element.has_role) {
    this.setIsLayoutTable();
    return; 
  }  

  if(this.is_data_table) return;

  this.is_data_table = true;

  if (this.parent_table_element) {
     
    if (this.parent_table_element.is_data_table) {
      this.layout_table_in_data_table = false; 
      this.data_table_in_data_table = true; 
    }
     
    this.layout_nesting_level = this.parent_table_element.layout_nesting_level + 1;   
     
  }
  
};

/**
 * @method setIsLayoutTable
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc  Set is a data table property, if not already set   
 */
 
OpenAjax.a11y.cache.TableElement.prototype.setIsLayoutTable = function () {

  this.is_data_table = false;

  if (this.parent_table_element) {
     
    if (this.parent_table_element.is_data_table) {
      this.layout_table_in_data_table = true; 
      this.data_table_in_data_table = false; 
    }
     
    this.layout_nesting_level = this.parent_table_element.layout_nesting_level + 1;   
     
  }
  
};


/**
 * @method getTableElementByCacheId 
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc  Retrieve table cache element from the tree of table cache elements
 *
 * @param  {String}  cache_id  -  cache_id of a table cache element 
 *
 * @return  {CaptionElement | TheadElement | TBodyElement | TableRowElement | TableCellElement | null}  Returns table cache element if cahce id is found, otherwise null
 */
OpenAjax.a11y.cache.TableElement.prototype.getTableElementByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};
 
/**
 * @method getItemByCacheId 
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc  Retrieve table cache element from the tree of table cache elements
 *
 * @param  {String}  cache_id  -  cache_id of a table cache element 
 *
 * @return  {CaptionElement | TheadElement | TBodyElement | TableRowElement | TableCellElement | null}  Returns table cache element if cahce id is found, otherwise null
 */
 
OpenAjax.a11y.cache.TableElement.prototype.getItemByCacheId = function (cache_id) {

   function traverseTableElements(table_elements) {
     var table_elements_len = table_elements.length;
     var to;
     var i;
     var ro;
     
     for (i = 0; i < table_elements_len; i++) {
       to = table_elements[i];
       
       if (to.cache_id == cache_id) {
         return to;
       }
       else {
         if (to.child_cache_elements && to.child_cache_elements.length) {
           ro = traverseTableElements(to.child_cache_elements);
           if (ro) return ro;
         }
       }
     } // end loop
   
     return null;
   }

   return traverseTableElements(this.child_cache_elements);
 
 };

/**
 * @method addTableElement 
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc    Adds a table cache element object to table_elements array and generates a cache id value for the table element object
 *
 * @param  {CaptionElement | TableTHeadElement | TableTBodyElement | TableRowElement | TableCellElement} table_element  - table cache element to add
 *
 * @return  {Number}  Returns the length the list of table elements 
 *
 */
 OpenAjax.a11y.cache.TableElement.prototype.addTableElement = function (table_element) {

   var caption;
   var summary;

   this.length = this.length + 1;
   table_element.document_order = this.length;
   table_element.cache_id = this.cache_id + "_te_" + this.length;
     
   this.table_elements.push(table_element);

   switch (table_element.table_type) {
   
   case OpenAjax.a11y.TABLE.CAPTION_ELEMENT:
     this.setIsDataTable();
     this.effective_caption                = table_element.name;
     this.effective_caption_for_comparison = table_element.name_for_comparision;
     break;
   
   case OpenAjax.a11y.TABLE.THEAD_ELEMENT:
     this.setIsDataTable();
     break;

   case OpenAjax.a11y.TABLE.TBODY_ELEMENT:
     break;

   case OpenAjax.a11y.TABLE.TR_ELEMENT:
     this.nextRow();   
     break;

   case OpenAjax.a11y.TABLE.TH_ELEMENT:
     this.setIsDataTable();

     if ((table_element.number_of_header_ids > 1) ||
         (table_element.row_span             > 1) ||
         (table_element.column_span          > 1)) {
       this.is_complex_data_table = true;        
     }

     this.addTableCellElement(table_element);
     break;   

   case OpenAjax.a11y.TABLE.TD_ELEMENT:  

//   OpenAjax.a11y.logger.debug("  Data Table Assumption: " + OpenAjax.a11y.DATA_TABLE_ASSUMPTION);

     if (this.is_data_table) {
       if ((table_element.number_of_header_ids > 1) ||
           (table_element.row_span             > 1) ||
           (table_element.column_span          > 1)) {
         this.setIsDataTable();
         this.is_complex_data_table = true;        
       }     
     } else {
       if ((this.max_row > 1) && 
           (this.max_column > 1) && 
           OpenAjax.a11y.DATA_TABLE_ASSUMPTION) {
           
         this.setIsDataTable();
       }
     }
     
     this.addTableCellElement(table_element);
     break;
     
   default:
     break;


   } // end switch

   return this.length;

 };

/**
 * @method addChild 
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 * 
 * @desc Adds a cache table element to the root tree representation of the tree cache
 *
 * @param  {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement }  table_element  - Cache table element object to add to root of tree of table elements 
 */

OpenAjax.a11y.cache.TableElement.prototype.addChild = function (table_element) {

 if (table_element) {
  this.child_cache_elements.push(table_element); 
 }  

}; 

/**
 * @method nextRow
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Updates the current table cell counters and array to start a new row in the table
 */
 OpenAjax.a11y.cache.TableElement.prototype.nextRow = function () {
 
   this.row = this.row + 1;
   this.max_row = this.row+1; // 1 based index
      
   // see if there is already a row created
   if (typeof(this.cells[this.row]) != 'object') {
     // If row does not exist create it
     this.cells[this.row] = [];
     this.cells[this.row][0] = null;
   }
   
   if (!this.is_complex_data_table && this.max_column > 2 ) {
	 this.multipleTHInRow(this.max_row-1); 	   
   } 
   
   if (!this.is_complex_data_table && this.max_row > 2) {
	 this.multipleTHInColumn(); 	   
   }
 };
 
/**
 * @method multipleTHInRow
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 * 
 * @desc Tests to see if there are multiple table header cells in a row and sets complex data table flag if there are
 * 
 * @param {Number} row - Number of the row to test for headers
 */
 OpenAjax.a11y.cache.TableElement.prototype.multipleTHInRow = function(row) {
   
   if (!this.cells[row] || this.cells[row].length < 2) return;
   
   var i;
   var th_count = 0;
   var td_count = 0;
   
   var row_len = this.cells[row].length;
   var cell;
   
   for (i=0; i<row_len; i++) {
	 cell = this.cells[row][i];
	
	 if (cell && 
	     cell.table_type == OpenAjax.a11y.TABLE.TH_ELEMENT) {
	   th_count++;
	 }
	 else {
	   td_count++;
	 }
   }   
   
   if (th_count > 1 && td_count > 0) {
     this.is_complex_data_table = true;
   }

 };

/**
 * @method multipleTHInColumn
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 * 
 * @desc Tests to see if there are multiple table header cells in a column and sets complex data table flag if there are
 * 
 * @param {Number} column - Number of the column to test for headers
 */
 OpenAjax.a11y.cache.TableElement.prototype.multipleTHInColumn = function() {
      
   var c, r;
   var th_count = 0;
   var td_count = 0;

   var row_max = this.max_row;
   var col_max = this.cells[0].length;
   var row_len;
   var cell;
   
   for (c=0; c<col_max; c++) {   
     th_count = 0;
     td_count = 0;
     
     for (r=0; r<row_max; r++) {
	   cell = this.cells[r][c];
	
  	   if (cell && 
  	       cell.table_type == OpenAjax.a11y.TABLE.TH_ELEMENT) {
	     th_count++;
	   }
	   else {
	     td_count++;
	   }
	 }
	 
     if (th_count > 1 && td_count > 0) {
       this.is_complex_data_table = true;
       return;
     }  
   }   
 };

/**
 * @method addTableCellElement
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Adds a TableCellElement to the current row
 *
 * @param {TableCellElement}  table_cell_element  -  The table cell element object to add in to the current row and column of a table 
 */
 OpenAjax.a11y.cache.TableElement.prototype.addTableCellElement = function (table_cell_element) {

   var i;
   var j;
   var r;
   var c;
   
   this.column = 0; 
  
   if (table_cell_element.id && 
       table_cell_element.id.length) {
     this.cell_ids.push(table_cell_element.id);
   }

   // find the next available spot in cells array, this needs to be calculated due to row anc olumn spanning 
   while ((this.cells[this.row][this.column] !== undefined) &&
          (this.cells[this.row][this.column] !== null)) {
     this.column++;
   } // end loop

   r = this.row;
   c = this.column;
     
   table_cell_element.row    = r;
   table_cell_element.column = c;

   for (i=0; i<table_cell_element.row_span; i++) {
     
     for (j=0; j<table_cell_element.column_span; j++) {
       this.cells[r][c] = table_cell_element;
       c += 1;
     }
     r += 1;
  
     // see if there is already a row created
     if (typeof(this.cells[r]) != 'object') {
       // If row does not exist create it
       this.cells[r] = [];
       this.cells[r][0] = null;
     } 
   }  
   this.setTableCellHeader(this.row, this.column, table_cell_element);

   if (c > this.max_column) this.max_column = c; 
   
 }; 

/**
 * @method sortCellIds
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 * 
 * @desc Sorts the cell ids array for this table based on the id values
 */
 OpenAjax.a11y.cache.TableElement.prototype.sortCellIds = function () {

   var swapped = false;
   var temp = null;
   var i;
 
   var cell_ids_len = this.cell_ids.length;

   do{
     swapped = false;
     
     for (i = 1; i < cell_ids_len; i++ ) {
     
       if (this.cell_ids[i-1] > this.cell_ids[i]) {
         
         // swap the values
         temp = this.cell_ids[i-1];
         this.cell_ids[i-1] = this.cell_ids[i];
         this.cell_ids[i] = temp;
         swapped = true;
       } 
     } // end loop
   } while (swapped);
 };

/**
 * @method setTableCellHeader
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Sets header content property of the table cell element object 
 *
 * @param {Number}            row                 - Current row position of the table cell element object
 * @param {Number}            column              - Current column position of the table cell element object
 * @param {TableCellElement}  table_cell_element  - Table cell element object to create header content property
 */
 OpenAjax.a11y.cache.TableElement.prototype.setTableCellHeader = function (row, column, table_cell_element) {

   var tag_name;   
   var scope;
   var string_array = [];
   var cell, r, c;

   tag_name = table_cell_element.dom_element.tag_name;
   scope  = table_cell_element.dom_element.scope;
   
   table_cell_element.header_source =  OpenAjax.a11y.HEADER_SOURCE.NONE;
 
   if (table_cell_element.headers) {
     table_cell_element.header_content = this.dom_cache.element_with_id_cache.getTextFromIds(table_cell_element.headers);
     if (table_cell_element.header_content.length) table_cell_element.header_source =  OpenAjax.a11y.HEADER_SOURCE.HEADERS_ATTRIBUTE;
   }
   else {
     // if a table cell is used as a header in the table and has no header attribute set its header to an empty string
     if (table_cell_element.table_type === OpenAjax.a11y.TABLE.TH_ELEMENT) {
       table_cell_element.header_content = "";
     }   
     else {

       // find TH or TD with scope=column in the same column
       for (r=(row-1); r>=0; r--) {
         cell = this.cells[r][column];
    
         if (cell) {
           tag_name = cell.dom_element.tag_name;
           scope  = cell.scope;
      
           if (tag_name == "th" || scope == "col") {
             if (!cell.cell_text) cell.cell_text = cell.dom_element.getText().normalizeSpace(); 
             string_array.push(cell.cell_text);
           }
         }     
       } 

       // find TH or TD with scope=row in the same row
       for (c=(column-1); c>=0; c--) {
         cell = this.cells[row][c];
    
         if (cell) {
           tag_name = cell.dom_element.tag_name;
           scope  = cell.scope;
          
           if (tag_name == "th" || scope == "row") {
             if (!cell.cell_text) cell.cell_text = cell.dom_element.getText().normalizeSpace(); 
             string_array.push(cell.cell_text);
           }
         } 
       } 
       table_cell_element.header_content = string_array.join(' ');
       
       if (table_cell_element.header_content.length) table_cell_element.header_source =  OpenAjax.a11y.HEADER_SOURCE.HEADERS_ATTRIBUTE;
     } 
   } 
 };

/**
 * @method findFirstRowWithContent
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Finds the first row of the table which has text content in at least one cell.
 *       This is used to skip rows that are used for stylistic puposes, since they usually
 *       do not have any text content other than spaces in them.
 *
 * @return {Number}  Returns number of first row with content  
 * 
 */
 OpenAjax.a11y.cache.TableElement.prototype.findFirstRowWithContent = function() {
 
   var r;
   var c;
   var max_row = this.max_row;
   var max_col;
   var text;
   var cell;
 
   for (r = 0; r < max_row; r++) {
     max_col = this.cells[r].length;
 
     for (c = 0; c < max_col; c++) {
       cell = this.cells[r][c];
       
       if (!cell || !cell.dom_element) continue;
       
       text = cell.dom_element.getText();
    
       if (text) text = text.normalizeSpace();
       
       if (cell.table_type == OpenAjax.a11y.TABLE.TH_ELEMENT || 
           text.length) {
         return r;
       }
     }
   }
   return -1;
 };

/**
 * @method findFirstColumnWithContent
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Finds the first column of the table which has text content in at least one cell.
 *       This is used to skip columns that are used for stylistic puposes, since they usually
 *       do not have any text content other than spaces in them.
 *
 * @return {Number}  Returns number of first column with content  
 * 
 */
 OpenAjax.a11y.cache.TableElement.prototype.findFirstColumnWithContent = function() {
 
   var r;
   var c;
   var max_col = this.max_column;
   var max_row = this.max_row;
   var text;
   var cell;
 
   for (c = 0; c < max_col; c++) {
 
     for (r = 0; r < max_row; r++) {
     
       cell = this.cells[r][c];
       
       if (!cell || !cell.dom_element) continue;
       
       text = cell.dom_element.getText();
       
       if (text) text = text.normalizeSpace();
       
       if (cell.table_type == OpenAjax.a11y.TABLE.TH_ELEMENT || 
           text.length) {
         return c;
       }
     }
   }
   return -1;
 };


/**
 * @method headerCellsInFirstRow
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Calculates the number of non-empty cells in the first row with content and
 *       how many of the non-empty cells are header cells
 *
 * @return {Object} Returns an object with two properties 'total' and 'th_count'  
 */
 OpenAjax.a11y.cache.TableElement.prototype.headerCellsInFirstRow = function () {

   // ro is the Return Object 
   var ro = {};
   ro.total = 0;
   ro.th_count = 0;

   var c;
   var max_col;
   var cell;
   var text;
 
   var r = this.findFirstRowWithContent();
   
   if (r < 0) return ro;
   
   if (this.cells[r]) {
   
     max_col = this.cells[r].length;
 
     for (c = 0; c < max_col;) {
       cell = this.cells[r][c];
     
       if (cell.table_type == OpenAjax.a11y.TABLE.TH_ELEMENT) {
         ro.total++;   
         ro.th_count++;   
       }
       else {
         text = cell.dom_element.getText();
   
         if (text) text = text.normalizeSpace();
   
         if (text.length) {
           ro.total++;
         } 
       }
       c += cell.column_span;
     }
   }  
   return ro;
 };

/** 
 * @method headerCellsInFirstColumn
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Calculates the number of non-empty cells in the first column with content and
 *       how many of the non-empty cells are header cells
 *
 * @return {Object} Returns an object with two properties 'total' and 'th_count'  
 */
 OpenAjax.a11y.cache.TableElement.prototype.headerCellsInFirstColumn = function () {

   // ro is the Return Object 
   var ro = {};
   ro.total = 0;
   ro.th_count = 0;
 
   var r;
   var c;
   var text;
   var cell;
   var max_row;
 
   c = this.findFirstColumnWithContent();
   
   if (c < 0) return ro; 
   
   max_row = this.max_row;
 
   for (r = 0; r < max_row;) {
     cell = this.cells[r][c];
     
     if (!cell) break;
     
     if (cell.table_type == OpenAjax.a11y.TABLE.TH_ELEMENT) {
       ro.total++;   
       ro.th_count++;   
     }
     else {
       text = cell.dom_element.getText();
   
       if (text) text = text.normalizeSpace();
   
       if (text.length) {
         ro.total++;
       } 
     }
     r += cell.row_span;
   } 
   return ro;
 };

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TableElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TableElement.prototype.getAttributes = function (unsorted) {
   
  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var attributes = this.dom_element.getAttributes(unsorted);
  
  attributes.push(cache_nls.getLabelAndValueNLS('summary', this.summary));
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.TableElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};


/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.TableElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.TableElement.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = this.dom_element.getCacheProperties();
  
  properties.push(cache_nls.getLabelAndValueNLS('is_data_table', this.is_data_table));
  properties.push(cache_nls.getLabelAndValueNLS('is_complex_data_table', this.is_complex_data_table));
  properties.push(cache_nls.getLabelAndValueNLS('effective_caption', this.effective_caption));
  properties.push(cache_nls.getLabelAndValueNLS('effective_summary', this.effective_summary));
  properties.push(cache_nls.getLabelAndValueNLS('max_row', this.max_row));
  properties.push(cache_nls.getLabelAndValueNLS('max_column', this.max_column));
  properties.push(cache_nls.getLabelAndValueNLS('nesting_level', this.nesting_level));
  
  this.dom_element.sortItems(properties);
  
  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.TableElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.TableElement
 *
 * @desc Creates a text string representation of the table element object 
 *
 * @return {String} Returns a text string representation of the table
 */
 OpenAjax.a11y.cache.TableElement.prototype.toString = function () {
   var str = "";
   
   if (this.is_data_table) {
     str += this.max_column + "x" + this.max_row + " Data Table: ";
     if (this.effective_caption.length) {
       str += this.effective_caption;
     } 
     else {
        if (this.effective_summary) {
          str += this.effective_summary; 
        }
        else {
          str += "no name";
        }
     }
   } 
   else {
     str += this.max_row + "x" + this.max_column + " Layout Table ";   
   }
   
   return str;
     
 };


/* ---------------------------------------------------------------- */
/*                         CaptionElement Object                    */
/* ---------------------------------------------------------------- */

/**
 * @constructor CaptionElement
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a caption element object which contains 
 *       information obout a caption element in a table
 *          
 * @param  {DOMElement}  dom_element  - Reference to the dom element object associated with caption element 
 * @param  {TableInfo}   table_info   - Information about the current table relationships in the DOM
 * 
 * @property  {DOMElement}  dom_element  - Reference to the dom element object associated with caption element 
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {TableElement}  parent_table_element  - Reference to the table element object that contatins the caption element
 *
 * @property  {Number}  type                 - Constant indicating the type of table cache element object
 *
 * @property  {String}  name                 - The text content of the caption element
 * @property  {String}  name_for_comparison  - The text content used for comparisons with other text content (i.e. lowercase, space normalized and trimmed)
 */
 
OpenAjax.a11y.cache.CaptionElement = function (dom_element, table_info) {

  this.dom_element = dom_element;

  var name  = dom_element.getText(); 
  this.name = name;
  this.name_for_comparison = name.normalizeSpace();
  
  this.table_type = OpenAjax.a11y.TABLE.CAPTION_ELEMENT;

  this.parent_table_element = table_info.table_element;
    
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.CaptionElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.CaptionElement.prototype.getAttributes = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var attributes = this.dom_element.getAttributes();
  
  return attributes;
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.CaptionElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.CaptionElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.CaptionElement.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = this.dom_element.getCacheProperties();
  
  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.CaptionElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.CaptionElement
 *
 * @desc Creates a text string representation of the caption element object 
 *
 * @return {String} Returns a text string representation of the caption element object
 */
 
 OpenAjax.a11y.cache.CaptionElement.prototype.toString = function () {
   return "caption: " + this.name;   
 };

/* ---------------------------------------------------------------- */
/*                         THeadElement Object                      */
/* ---------------------------------------------------------------- */

/**
 * @constructor THeadElement
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a thead element object which contains 
 *       information obout a thead element in a table
 *          
 * @param  {DOMElement}  dom_element  - Reference to the dom element object associated with thead element 
 * @param  {TableInfo}   table_info   - Information about the current table relationships in the DOM
 * 
 * @property  {DOMElement}  dom_element  - Reference to the dom element object associated with thead element 
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {Array}         child_cache_elements  - Array of table cache elements for the tree representation of the table
 * @property  {TableElement}  parent_table_element  - Reference to the table element object that contatins the thead element
 *
 * @property  {Number}  type       - Constant indicating the type of table cache element object
 *
 * @property  {Number}  row_count  - Number of table rows contained in the childresn of the thead element
 */
 
OpenAjax.a11y.cache.THeadElement = function (dom_element, table_info) {

  this.dom_element = dom_element;
  this.cache_id    = "";
  
  this.child_cache_elements = [];
  this.parent_table_element = table_info.table_element;

  this.table_type = OpenAjax.a11y.TABLE.THEAD_ELEMENT;
  
  this.row_count = 0;

};

/**
 * @method addChild
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 * 
 * @desc Adds a cache table element to the tree representation of the table in the cache
 *
 * @param  {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement }  table_element  - Cache table element object to add to root of tree of table elements 
 */

OpenAjax.a11y.cache.THeadElement.prototype.addChild = function (child_object) {

  if (child_object) {
    this.child_cache_elements.push(child_object); 
  }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.THeadElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.THeadElement.prototype.getAttributes = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var attributes = this.dom_element.getAttributes();
  
  return attributes;
};



/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.THeadElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.THeadElement.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = this.dom_element.getCacheProperties();
  
  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.THeadElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.THeadElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.THeadElement
 *
 * @desc Creates a text string representation of the thead element object 
 *
 * @return {String} Returns a text string representation of the thead element object
 */
 OpenAjax.a11y.cache.THeadElement.prototype.toString = function () {
   var str = "thead: " + this.row_count + " rows";   
   
   if (this.row_count === 1 ) str =  "thead: " + this.row_count + " row";
   
   return str;
 };


/* ---------------------------------------------------------------- */
/*                         TBodyElement Object                      */
/* ---------------------------------------------------------------- */

/**
 * @constructor TBodyElement
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a tbody element object which contains 
 *       information obout a tbody element in a table
 *          
 * @param  {DOMElement}  dom_element  - Reference to the dom element object associated with tbody element 
 * @param  {TableInfo}   table_info   - Information about the current table relationships in the DOM
 * 
 * @property  {DOMElement}  dom_element  - Reference to the dom element object associated with tbody element 
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {Array}         child_cache_elements  - Array of table cache elements for the tree representation of the table
 * @property  {TableElement}  parent_table_element  - Reference to the table element object that contatins the tbody element
 *
 * @property  {Number}  type                 - Constant indicating the type of table cache element object
 *
 * @property  {Number}  row_count            - Number of table rows contained in the childresn of the tbody element
 */
 
OpenAjax.a11y.cache.TBodyElement = function (dom_element, table_info) {

  this.dom_element          = dom_element;
  this.child_cache_elements = [];
  this.parent_table_element = table_info.table_element;

  this.table_type = OpenAjax.a11y.TABLE.TBODY_ELEMENT;

  this.row_count = 0;

};

/**
 * @method addChild
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 * 
 * @desc Adds a cache table element to the tree representation of the table in the cache
 *
 * @param  {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement }  table_element  - Cache table element object to add to root of tree of table elements 
 */

OpenAjax.a11y.cache.TBodyElement.prototype.addChild = function (child_object) {

 if (child_object) {
  this.child_cache_elements.push(child_object); 
 }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TBodyElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TBodyElement.prototype.getAttributes = function () {
  return this.dom_element.getAttributes();
};


/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.TBodyElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.TBodyElement.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = this.dom_element.getCacheProperties();
  
  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.TBodyElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.TBodyElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.TBodyElement
 *
 * @desc Creates a text string representation of the tbody element object 
 *
 * @return {String} Returns a text string representation of the tbody element object
 */
 OpenAjax.a11y.cache.TBodyElement.prototype.toString = function () {
   var str = "tbody: " + this.row_count + " rows";   
   
   if (this.row_count === 1 ) str =  "tbody: " + this.row_count + " row";
   
   return str;
 };


/* ---------------------------------------------------------------- */
/*                       TableRowElement Object                     */
/* ---------------------------------------------------------------- */

/**
 * @constructor TableRowElement
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a table row element object which contains 
 *       information obout a tr element in a table
 *          
 * @param  {DOMElement}  dom_element  - Reference to the dom element object associated with tr element 
 * @param  {TableInfo}   table_info   - Information about the current table relationships in the DOM
 * 
 * @property  {DOMElement}  dom_element  - Reference to the dom element object associated with tr element 
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {Array}         child_cache_elements  - Array of table cache elements for the tree representation of the table
 * @property  {TableElement}  parent_table_element  - Reference to the table element object that contatins the tr element
 *
 * @property  {Number}  type               - Constant indicating the type of table cache element object
 * @property  {String}  cache_id           - String that uniquely identifies the cache element in the DOMCache
 *
 * @property  {Number}  header_cell_count  - Number of header cells in the row
 * @property  {Number}  data_cell_count    - Number of data cells in the row
 */
 
OpenAjax.a11y.cache.TableRowElement = function (dom_element, table_info) {

  this.dom_element  = dom_element;
  this.cache_id     = "";
  
  this.child_cache_elements = [];
  this.parent_table_element = table_info.table_element;

  this.table_type = OpenAjax.a11y.TABLE.TR_ELEMENT;

  this.header_cell_count = 0;
  this.data_cell_count   = 0;
 
};

/**
 * @method addChild
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 * 
 * @desc Adds a cache table element to the tree representation of the table in the table cache
 *
 * @param  {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement }  table_element  - Cache table element object to add to root of tree of table elements 
 */

OpenAjax.a11y.cache.TableRowElement.prototype.addChild = function (child_object) {

 if (child_object) {
  this.child_cache_elements.push(child_object); 
 }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TableRowElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TableRowElement.prototype.getAttributes = function () {
  return this.dom_element.getAttributes();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.TableRowElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.TableRowElement.prototype.getCacheProperties = function () {

  return this.dom_element.getCacheProperties();
  
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.TableRowElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};

/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.TableRowElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.TableRowElement
 *
 * @desc Creates a text string representation of the tr element object 
 *
 * @return {String} Returns a text string representation of the tr element object
 */
 OpenAjax.a11y.cache.TableRowElement.prototype.toString = function () {
 
   var str =  "tr: ";
 
   if (this.header_cell_count && this.data_cell_count) {
     if (this.header_cell_count === 1) str += " 1 header cell and ";
     else str += this.header_cell_count + " header cells and ";
 
     if (this.data_cell_count === 1) str += " 1 data cell";
     else str += this.data_cell_count + " data cells";
   }
   else {
     if (this.header_cell_count) {
       if (this.header_cell_count === 1) str += " 1 header cell";
       else str += this.header_cell_count + " header cells";
     }
     else {
       if (this.data_cell_count) {
         if (this.data_cell_count === 1) str += " 1 data cell";
         else str += this.data_cell_count + " data cells";
       }
       else {
         str += " no table cells ";       
       }
     }  
   }

   return str;
 };


/* ---------------------------------------------------------------- */
/*                            TableCellElement                      */
/* ---------------------------------------------------------------- */

/**
 * @constructor TableCellElement
 * 
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Create a table cell element object which contains 
 *       information obout a td or th element in a table
 *          
 * @param  {DOMElement}  dom_element  - Reference to the dom element object associated with td or th element 
 * @param  {TableInfo}   table_info   - Information about the current table relationships in the DOM
 * 
 * @property  {DOMElement}  dom_element  - Reference to the dom element object associated with td or th element 
 * @property  {String}      cache_id     - String that uniquely identifies the cache element object in the cache
 *
 * @property  {Array}            child_cache_elements  - Array of table cache elements for the tree representation of the table
 * @property  {TableElement}     parent_table_element  - Reference to the table element object that contatins the td or th element
 * @property  {TableRowElement}  parent_row_element    - Reference to the table element object that contatins the td or th element
 *
 * @property  {Number}  type               - Constant indicating the type of table cache element object
 *
 * @property  {String}  text_content          - Text content of the element including descendent element content
 * @property  {String}  scope                 - Value of the scope attribute
 * @property  {String}  headers               - Value of the headers attribute
 * @property  {Array}   headers_array         - Array of id values in the headers attribute
 * @property  {String}  header_content        - Text content of calculated headers
 * @property  {Number}  header_source         - How header content was calculated    
 * @property  {Number}  number_of_header_ids  - Number of ids in the headers attribute    
 *
 * @property  {Boolean} has_spans          - Value of the rowspan attribute
 * @property  {Number}  row_span           - Value of the rowspan attribute (Note: converted to Number)
 * @property  {Number}  column_span        - Value of the colspan attribute (Note: converted to Number)
 */
 
OpenAjax.a11y.cache.TableCellElement = function (dom_element, table_info) {

  var headers_array = [];  // array of id headers
  var is_th;
   
  this.dom_element  = dom_element;
  this.cache_id     = "";
  
  this.parent_table_element = table_info.table_element;
  this.parent_row_element   = table_info.table_row_element;
  
  this.child_cache_elements = [];   
  
  var text_content = dom_element.getText();   
  this.text_content = text_content;
  if (typeof this.text_content === 'string') this.text_content_for_comparison = text_content.normalizeSpace().toLowerCase();
  else this.text_content_for_comparison = "";
    
  this.table_type = OpenAjax.a11y.TABLE.TD_ELEMENT;

  is_th = dom_element.tag_name == 'th';
  this.scope = dom_element.node.getAttribute('scope');   
  
  if (is_th) {
    this.table_type = OpenAjax.a11y.TABLE.TH_ELEMENT;
  }
  else {
    if (this.scope) {
      this.scope = this.scope.toLowerCase();
      
      if (this.scope == 'row' || this.scope == 'col') {
       this.table_type = OpenAjax.a11y.TABLE.TH_ELEMENT;     
      }
    }
  }

  if (table_info.table_row_element) {
    if (this.table_type === OpenAjax.a11y.TABLE.TD_ELEMENT) {
      table_info.table_row_element.data_cell_count++;
    }
    else{
      table_info.table_row_element.header_cell_count++;    
    }
  }  

  this.headers = dom_element.node.getAttribute('headers');

  this.number_of_header_ids = 0;
  
  if (this.headers && this.headers.length > 0) {
    this.headers_array = this.headers.split(" ");
    
    this.number_of_header_ids = this.headers_array.length;    
  }
   
  this.row_span   = dom_element.node.getAttribute('rowspan');
   
  if (typeof this.row_span === 'string') { 
    this.has_spans = true;
    this.row_span   = parseInt(this.row_span,10);
  } 
  else {
    this.row_span   = 1;
  }
  
  this.column_span   = dom_element.node.getAttribute('colspan');    
   
  if (typeof this.column_span === 'string') { 
    this.has_spans = true;
    this.column_span   = parseInt(this.column_span,10);
  } else {
    this.column_span   = 1;
  }
  
}; 


/**
 * @method addChild
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 * 
 * @desc Adds a cache table element to the tree representation of the table in the cache
 *
 * @param  {TableElement | CaptionElement | THeadElement | TBodyElement | TableRowElement | TableCellElement }  table_element  - Cache table element object to add to root of tree of table elements 
 */

OpenAjax.a11y.cache.TableCellElement.prototype.addChild = function (table_element) {

 if (table_element) {
  this.child_cache_elements.push(table_element); 
 }  

}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TableCellElement.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style items
 */

OpenAjax.a11y.cache.TableCellElement.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};


/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.TableCellElement.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  cache_nls.addPropertyIfDefined(attributes, this, 'row_span');
  cache_nls.addPropertyIfDefined(attributes, this, 'column_span');
  cache_nls.addPropertyIfDefined(attributes, this, 'headers');
  cache_nls.addPropertyIfDefined(attributes, this, 'scope');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @return {Array} Returns a array of cache properties
 */

OpenAjax.a11y.cache.TableCellElement.prototype.getCacheProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;
  
  var properties = this.dom_element.getCacheProperties();
  
  cache_nls.addPropertyIfDefined(properties, this, 'table_type');
  cache_nls.addPropertyIfDefined(properties, this, 'header_content');
  cache_nls.addPropertyIfDefined(properties, this, 'header_source');
  cache_nls.addPropertyIfDefined(properties, this, 'text_content');
  
  this.dom_element.sortItems(properties);
  
  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.TableCellElement.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};


/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event information
 */

OpenAjax.a11y.cache.TableCellElement.prototype.getEvents = function (unsorted) {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.TableCellElement
 *
 * @desc Creates a text string representation of the table cell element object 
 *
 * @return {String} Returns a text string representation of the table cell element object
 */
OpenAjax.a11y.cache.TableCellElement.prototype.toString = function () {
  var text = this.dom_element.getText();
  var tag_name = this.dom_element.tag_name;
  
  if (this.parent_table_element.is_data_table) {

    if (text.length) {
      return tag_name + ": " + text;
    }
    else {
      return tag_name + ": empty cell";
    }
  }
  else {
    var str = tag_name + "(for layout) contains: ";
    
    var count = this.dom_element.getElementCount();
    
    if (count === 1) str += "1 element and ";
    else str += count + " elements and ";    
    
    count = text.length;
    
    if (count === 1) str += "1 character";
    else str += count + " characters";    

    return str;    
  }
};

/* ---------------------------------------------------------------- */
/*                       PageElementLayout                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor PageElementLayout
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a body element object used to hold information about a title element
 *
 * @param  {DOMelement}   dom_element      - The dom element object representing the heading element 
 * @param  {MainElement}  parent_landmark  - This is always null since this is the root element
 *
 * @property  {DOMElement}   dom_element      - Reference to the dom element representing the optgroup element
 * @property  {String}       cache_id         - String that uniquely identifies the cache element object in the cache
 * @property  {Number}       document_order   - Ordinal position of the title and main cache items in the document to other title and main cache items
 *
 * @property  {Array}  child_cache_elements  - List of child cache title element, main landmarks and h1 heading element objects as part of cache title and main elements tree  
 *
 * @property  {Boolean}  is_page_element  -  Boolean indicating the element is a page element 
 *
 */

OpenAjax.a11y.cache.PageElementLayout = function (dom_element) {

  this.dom_element     = dom_element;
  this.cache_id        = "page_layout";
  this.document_order  = 0;
  this.is_page_element = true;

  this.child_cache_elements = []; // this is always empty for the body element

}; 

/**
 * @method addChildMainElement
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Adds a main landmark  object to the tree of title and main elements  
 *
 * @param {MainElement}  main_element  -  Main landmark element object to add 
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.addChildMainElement = function (main_element) {

  if (main_element) {
    this.child_cache_elements.push(main_element); 
  }  

};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};



/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.PageElementLayout.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.PageElementLayout
 *
 * @desc Returns a text string representation of the title element 
 *
 * @return {String} Returns string represention the title element object
 */
  
OpenAjax.a11y.cache.PageElementLayout.prototype.toString = function () {
  return "page";  
};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                            TextCache                            */
/* ---------------------------------------------------------------- */

/**
 * @constructor TextCache
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates cache object representing information related to text nodes in a document
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object 
 *         
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {Array}    text_nodes     - List of text nodes in the document 
 * @property {Number}   length         - Number of image element objects in the list 
 */
  
OpenAjax.a11y.cache.TextCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  this.up_to_date = false;
 
  this.text_nodes = [];
  this.length = 0;
 
}; 

/**
 * @method addTextNode
 * 
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Adds a text node to the list of image elements and generates a cache id for the object.
 *
 * @param  {DOMText}  text_node  - text_node object to add 
 *
 * @return {Number} Returns the length of the list of image element objects
 */

OpenAjax.a11y.cache.TextCache.prototype.addTextNode = function (text_node) {


  // item must exist and have the position property
  if (text_node) {
  
    var pe = text_node.parent_element;
    
    if (pe.tag_name !== 'script' && pe.tag_name !== 'style' &&  pe.tag_name !== 'object') {
      this.length = this.length + 1;
      text_node.document_order = this.length;
      this.text_nodes.push(text_node);
    }  
  } 

  return this.length;

};


/**
 * @deprecated getTextNodeByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Finds the the text node object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of image element object
 *
 * @return {DOMText | null} Returns cache text node object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.TextCache.prototype.getTextNodeByCacheId = function (cache_id) {
  return this.getItemByCacheId(cache_id);
};

/**
 * @method getItemByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Finds the the text node object with the matching cache id
 *
 * @param  {String}  cache_id  - Cache id of text node object
 *
 * @return {ImageElement | null} Returns cache text node object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.TextCache.prototype.getItemByCacheId = function (cache_id) {

  var i;
  var text_nodes_len = this.text_nodes.length;

  if (cache_id && cache_id.length) {  
    for (i=0; i < text_nodes_len; i++) {
      if (this.text_nodes[i].cache_id == cache_id) {
        return this.text_nodes[i];
      }
    } // end loop
  } 

 return null;
};


/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Resests the TextCache object properties and empties all the lists and arrays 
 */

OpenAjax.a11y.cache.TextCache.prototype.emptyCache = function () {

  this.text_nodes.length = 0;
  this.up_to_date = false;

};

/**
 * @method updateCacheItems
 *
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Updates the images cache object by checking to see if a dom element
 *          should be added to the cache
 *  
 * @param  {DOMElement}   dom_element   - dom element object to check for inclusion in images cache
 */
 
OpenAjax.a11y.cache.TextCache.prototype.updateCacheItems = function (dom_element) {

  if (dom_element.type === Node.TEXT_NODE) {
  
//    OpenAjax.a11y.logger.debug("  ADDED TEXT: " + dom_element.text );
  
    this.addTextNode(dom_element);
  
  }
  
};

/**
 * @method traverseDOMElementsForTextNodes
 *
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Traverses DOM Element objects in the tree to update the text cache 
 *
 * @param  {DOMElement}  dom_element - dom element object to check for inclusion in images cache
 */
 
OpenAjax.a11y.cache.TextCache.prototype.traverseDOMElementsForTextNodes = function (dom_element) {

  if (!dom_element) return;

  if (dom_element.type == Node.ELEMENT_NODE) {

    for (var i = 0; i < dom_element.child_dom_elements.length; i++ ) {
      this.traverseDOMElementsForTextNodes(dom_element.child_dom_elements[i]);
    } // end loop
  }
  else {
     this.updateCacheItems(dom_element);
  }
  
}; 

/**
 * @method updateCache
 *
 * @memberOf OpenAjax.a11y.cache.TextCache
 *
 * @desc Traverses the DOMElements to update the text cache
 *       NOTE: This function is only used when the specialized caches
 *       are build as rules need them.  In this condition, if the rules 
 *       dependent on the links cache are disabled, this cache would 
 *       not be updated
 */
 
OpenAjax.a11y.cache.TextCache.prototype.updateCache = function () {
  var i;
  var children = this.dom_cache.element_cache.child_dom_elements;
  var children_len = children.length;
 
  for (i=0; i < children_len; i++) {
    this.traverseDOMElementsForTextNodes(children[i]);
  }  

  this.up_to_date = true;
};

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                            KeyboardFocus                             */
/* ---------------------------------------------------------------- */

/**
 * @constructor KeyboardFocus
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates cache object representing information related to links in a web page
 *
 * @param {DOMCache}   dom_cache   - Reference to the DOMCache object 
 * 
 * @property {DOMCache} dom_cache  - Reference to the DOMCache object       
 * @property {Boolean}  up_to_date - Boolean true if the cache has been creating using the current DOMElements, else false
 *                                   NOTE: This is a common property of all caches and is used when selectively build caches 
 *                                         based on whether a rule needs the cache
 *
 * @property {Array}    area_elements  - List of area element objects in the document 
 * @property {Array}    link_elements  - List of link element objects in the document 
 * @property {Number}   length         - Number of link element objects in the list 
 *
 * @property {String}   this.sort_property   - Link element object property the list of link objects is sorted by
 * @property {Boolean}  this.sort_ascending  - true if list is sorted in ascending order, otherwise false
 *
 * @property {Array}    links_sorted_by_href  - List of link element object sorted by href values;
 * @property {Array}    links_sorted_by_name  - List of link element object sorted by there accessible name (i.e link text);
 *  
 * @property {Boolean}  sorted_by_href_ready  - True if list of link element objects sorted by href values is ready for use in rules
 * @property {Boolean}  sorted_by_name_ready  - True if list of link element objects sorted by name values is ready for use in rules
 */

OpenAjax.a11y.cache.KeyboardFocusCache = function (dom_cache) {

  this.dom_cache = dom_cache;
  this.up_to_date = false;
  this.page_element = null;
  this.interactive_elements = [];
  
}; 



/**
 * @method createKeyboardFocusCache
 * 
 * @memberOf OpenAjax.a11y.cache.KeyboardFocusCache
 *
 * @desc Populates the keyboard focus cache from the link, controls and media caches
 */

OpenAjax.a11y.cache.KeyboardFocusCache.prototype.createKeyboardFocusCache = function () {

  OpenAjax.a11y.logger.debug("  Page Element: " + this.dom_cache.element_cache.getPageElement());


  this.page_element = new OpenAjax.a11y.cache.PageElementKeyboardFocus(this.dom_cache.element_cache.getPageElement());
  
  this.interactive_elements = this.dom_cache.links_cache.link_elements;
  this.interactive_elements = this.interactive_elements.concat(this.dom_cache.controls_cache.control_elements);
//  this.interactive_elements = this.interactive_elements.concat(this.dom_cache.controls_cache.widget_elements);
  this.interactive_elements = this.interactive_elements.concat(this.dom_cache.media_cache.object_elements);  
  
};


/**
 * @method getItemByCacheId
 * 
 * @memberOf OpenAjax.a11y.cache.KeyboardFocusCache
 *
 * @desc Finds the the link element object with the matching cache id
 *
 * @param  {String }  cache_id  - Cache id of link element object
 *
 * @return {LinkElement} Returns cache link element object if cache id is found, otherwise null
 */

OpenAjax.a11y.cache.KeyboardFocusCache.prototype.getItemByCacheId = function (cache_id) {

  var i;

  var link_elements_len = this.link_elements.length;

  if (cache_id && cache_id.length) {  
   for (i=0; i < link_elements_len; i++) {
     if (this.link_elements[i].cache_id == cache_id) {
       return this.link_elements[i];
     }
   } // end loop
 } 

 return null;
};

/**
 * @method emptyCache
 *
 * @memberOf OpenAjax.a11y.cache.KeyboardFocusCache
 *
 * @desc Resests the KeyboardFocus object properties and empties all the lists and arrays 
 */
 
OpenAjax.a11y.cache.KeyboardFocusCache.prototype.emptyCache = function () {

  this.interactive_elements = [];
  this.length = 0;
  this.up_to_date = false;

};


/* ---------------------------------------------------------------- */
/*                       PageElementKeyboardFocus                               */ 
/* ---------------------------------------------------------------- */

/**
 * @constructor PageElementKeyboardFocus
 *
 * @memberOf OpenAjax.a11y.cache
 *
 * @desc Creates a body element object used to hold information about a title element
 *
 * @param  {DOMelement}   dom_element      - The dom element object representing the page element 
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus = function (dom_element) {

  this.dom_element     = dom_element;
  
}; 

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.getNodeResults = function () {
  return this.dom_element.getNodeResults();
};

/**
 * @method getStyle
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns an array of style items 
 *
 * @return {Array} Returns a array of style display objects
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.getStyle = function () {

  return this.dom_element.getStyle();
  
};

/**
 * @method getAttributes
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns an array of attributes for the element, sorted in alphabetical order 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of attribute display object
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.getAttributes = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;
  var attributes = this.dom_element.getAttributes();
  
//  cache_nls.addPropertyIfDefined(attributes, this, 'tag_name');
  
  if (!unsorted) this.dom_element.sortItems(attributes);
  
  return attributes;
};

/**
 * @method getCacheProperties
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns an array of cache properties sorted by property name 
 *
 * @param {Boolean}  unsorted  - If defined and true the results will NOT be sorted alphabetically
 *
 * @return {Array} Returns a array of cache property display object
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.getCacheProperties = function (unsorted) {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var properties = this.dom_element.getCacheProperties(unsorted);
 
  if (!unsorted) this.dom_element.sortItems(properties);

  return properties;
};

/**
 * @method getCachePropertyValue
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns the value of a property 
 *
 * @param {String}  property  - The property to retreive the value
 *
 * @return {String | Number} Returns the value of the property
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.getCachePropertyValue = function (property) {

  if (typeof this[property] == 'undefined') {
    return this.dom_element.getCachePropertyValue(property);
  }
  
  return this[property];
};



/**
 * @method getEvents
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns an array of events for the element, sorted in alphabetical order 
 *
 * @return {Array} Returns a array of event item display objects
 */

OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.getEvents = function () {
   
  return this.dom_element.getEvents();
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.cache.PageElementKeyboardFocus
 *
 * @desc Returns a text string representation of the title element 
 *
 * @return {String} Returns string represention the title element object
 */
  
OpenAjax.a11y.cache.PageElementKeyboardFocus.prototype.toString = function () {
  return "page";  
};


/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** 
 * @namespace OpenAjax.a11y.info
 */

OpenAjax.a11y.info = OpenAjax.a11y.info || {};

/* ---------------------------------------------------------------- */
/*                             RuleInfoItem                  */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor RuleInfoItem
 *
 * @memberOf OpenAjax.a11y.information
 *
 * @desc Constructor for an object that contains a title, url and description
 *       of some item of information associated with a rule or rule set
 *
 * @param  {Number}  c  - Constant indicating the type of information
 * @param  {String}  t  - Text of the infomration ite, that can be used as 
 *                        the text of a link 
 * @param  {String}  u  - A url to more information on the item   
 * @param  {String}  d  - A longer desctiption of the item
 *
 * @property  {String}  style        - A string that identifies the type of information<br/>
 *                                     The following are the possible values for style:<br/>
 *                                       authoring_tool<br/>
 *                                       example<br/>
 *                                       library_product<br/>
 *                                       manual_check<br/>
 *                                       other<br/>
 *                                       purpose<br/>
 *                                       rule_category<br/>
 *                                       specfication<br/>
 *                                       technique<br/>
 *                                       wcag_technique<br/>
 *        
 * @property  {String}  title        - Text describing the reference, the text 
 *                                     can be used as the text of a link 
 * @property  {String}  url          - A url to more information on the item, if no 
 *                                     this property will be an empty string
 */

OpenAjax.a11y.info.RuleInfoItem = function (c, t, u) {

  var REFS = OpenAjax.a11y.REFERENCES;

  var tc    =  0;
  var style = "";
  var title = "";
  var url   = "";
  
  if (typeof c === 'number') {
    tc = c;
    
    switch (c) {
    
    case REFS.AUTHORING_TOOL:
      style = "authoring_tool";
      break;
      
    case REFS.EXAMPLE:
      style = "example";
      break;
      
    case REFS.LIBRARY_PRODUCT:
      style = "library_product";
      break;
      
    case REFS.MANUAL_CHECK:
      style = "manual_check";
      break;
      
    case REFS.OTHER:
      style = "other";
      break;
      
    case REFS.PURPOSE:
      style = "purpose";
      break;
      
    case REFS.REFERENCE:
      style = "reference";
      break;
      
    case REFS.RULE_CATEGORY:
      style = "rule_category";
      break;
      
    case REFS.SPECIFICATION:
      style = "specfication";
      break;
      
    case REFS.TECHNIQUE:
      style = "technique";
      break;
      
    case REFS.WCAG_TECHNIQUE:
      style = "wcag_technique";
      break;
      
    default:
      break;
    }
  }
  
  if (typeof t === 'string') title = OpenAjax.a11y.util.transformElementMarkup(t);
  if (typeof u === 'string') url = u;
  
  return {
     get style           () { return style; },
     get title           () { return title; },
     get url             () { return url;   },
     toString : function () { return title; }
  };
  
};


/* ---------------------------------------------------------------- */
/*                             RuleResultsGroupInfo                 */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor RuleResultsGroupInfo
 *
 * @memberOf OpenAjax.a11y.information
 *
 * @desc Constructor for an object that contains a title, url and description
 *       for a group of rule results
 *
 * @param  {String}  t    - A title that can be used as the text of a link 
 * @param  {String}  u    - A url to more information on the item   
 * @param  {String}  d    - A longer description of the item
 * @param  {Number}  req  - Number of required rules 
 * @param  {Number}  rec  - Number of recommended rules 
 *
 * @property  {String}  title             - A short description that can be used as 
 *                                          a title or the text of a link 
 * @property  {String}  url               - A url to more information on the item   
 * @property  {String}  description       - A longer desctiption of the item
 *
 * @property  {Number}  required_rules    - Number of required rules in the ruleset
 * @property  {Number}  recommended_rules - Number of recommeneded rules in the ruleset
 * @property  {Number}  total_rules       - Total Number of rules in the ruleset
 */

OpenAjax.a11y.info.RuleResultsGroupInfo = function (t, u, d, req, rec) {

  var title  = "";
  var url    = "";
  var desc   = "";
  var r1     = 0;
  var r2     = 0;
  
  if (typeof t === 'string') title = OpenAjax.a11y.util.transformElementMarkup(t);
  if (typeof u === 'string') url   = u;
  if (typeof d === 'string') desc  = OpenAjax.a11y.util.transformElementMarkup(d);
  
  if (typeof req === 'number') r1  = req;
  if (typeof rec === 'number') r2  = rec;
  
  return {
     get title             ()  { return title;   },
     get url               ()  { return url;     },
     get description       ()  { return desc;    },
     get required_rules    ()  { return r1;      },
     get recommended_rules ()  { return r2;      },
     get total_rules       ()  { return r1 + r2; },
     toString : function   ()  { return title;   },
     
     addToRequiredCount    : function (n) { r1 += n; },
     addToRecommendedCount : function (n) { r2 += n; }
  };
  
};

/* ---------------------------------------------------------------- */
/*                                RulesetInfo                       */
/* ---------------------------------------------------------------- */
 
/** 
 * @constructor RulesetInfo
 *
 * @memberOf OpenAjax.a11y.information
 *
 * @desc Constructor for an object that contains a title, url and description
 *       for a group of rule results
 *
 * @param  {String}  i    - ID of the ruleset 
 * @param  {String}  t    - A title that can be used as the text of a link 
 * @param  {String}  u    - A url to more information on the item   
 * @param  {String}  d    - A longer description of the item
 * @param  {Number}  r1   - Number of required rules
 * @param  {Number}  r2   - Number of recommended rules
 * @param  {String}  a1   - Author of ruleset
 * @param  {String}  a2   - Link to more information about the ruleset
 * @param  {String}  l    - Date last updated
 * @param  {String}  v    - Version of ruleset
 *
 * @property  {String}  id                - ID of the ruleset 
 * @property  {String}  title             - A short description that can be used as 
 *                                          a title or the text of a link 
 * @property  {String}  url               - A url to more information on the item   
 * @property  {String}  description       - A longer desctiption of the item
 *
 * @property  {Number}  required_rules    - Number of required rules in the ruleset
 * @property  {Number}  recommended_rules - Number of recommeneded rules in the ruleset
 * @property  {Number}  total_rules       - Total Number of rules in the ruleset
 *
 * @property  {String}  author            - Name of the author of the ruleset
 * @property  {String}  author_url        - URL to more information about the author 
 *                                          of the ruleset
 * @property  {String}  date              - Date the ruleset was last updated
 * @property  {String}  version           - Version of the ruleset
 */

OpenAjax.a11y.info.RulesetInfo = function (i, t, u, d, r1, r2, a1, a2, l, v) {

  var id       = "";
  var title    = "";
  var url      = "";
  var desc     = "";
  var req      = 0;
  var rec      = 0;
  var auth     = "";
  var auth_url = "";
  var date     = "";
  var ver      = "";
  
  if (typeof i  === 'string') id    = i;
  if (typeof t  === 'string') title = OpenAjax.a11y.util.transformElementMarkup(t);
  if (typeof u  === 'string') url   = u;
  if (typeof d  === 'string') desc  = OpenAjax.a11y.util.transformElementMarkup(d);
  
  if (typeof r1 === 'number') req  = r1;
  if (typeof r2 === 'number') rec  = r2;
  
  if (typeof a1 === 'string') auth     = a1;
  if (typeof a2 === 'string') auth_url = a2;

  if (typeof l  === 'string') date  = l;
  if (typeof v  === 'string') ver   = v;

  return {
     get id                () { return id;    },
     get title             () { return title;    },
     get url               () { return url;      },
     get description       () { return desc;     },
     get required_rules    () { return r1;       },
     get recommended_rules () { return r2;       },
     get total_rules       () { return r1 + r2;  },
     get author            () { return auth;     },
     get author_url        () { return auth_url; },
     get date              () { return date;     },
     get version           () { return ver;      },
     toString : function   () { return title;    }
  };
  
};


/* ---------------------------------------------------------------- */
/*                    SuccessCriterionInfo                */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor SuccessCriterionInfo
 *
 * @memberOf OpenAjax.a11y.information
 *
 * @desc Constructor for an object that contains information about a WCAG20
 *       Principle, Guideline or Success Criterion
 *
 * @param  {String}  id - ID in the format "P.G.SC" for the WCAG 2.0 item to get information
 *
 * @property  {String}  id           - String id of the principle, guideline or success 
 *                                     criteria (i.e. P.G.SC format)
 * @property  {String}  title        - A title that can be used as the text of a link 
 * @property  {String}  url          - A url to more information on the item   
 * @property  {String}  description  - A longer desctiption of the item
 * @property  {String}  level        - A longer desctiption of the item 
 *                                     (level is only defined for SC, oterwise emtpy)
 */
 
OpenAjax.a11y.info.SuccessCriterionInfo = function (id) {

  var wid   = "";
  var title = "";
  var url   = "";
  var desc  = "";
  var level = "";
  
  var wcag20_nls = OpenAjax.a11y.all_wcag20_nls.getNLS();
  
  var sc = wcag20_nls.getNLSItemById(id);
  
  if (sc && (typeof sc.sc_id === 'string')) {
    wid   = sc.sc_id;
    level = wcag20_nls.levels[sc.level];
    title = sc.title + " (" + level + ")";
    url   = sc.url_spec;
    desc  = sc.description;
  }  
  
  return {
     get id          () { return wid;   },
     get title       () { return title; },
     get url         () { return url;   },
     get description () { return desc;  },  
     get level       () { return level; }, 
     
     toString : function() { return title; }
  };
  
};


/* ---------------------------------------------------------------- */
/*                             ElementSummary                       */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor ElementSummary
 *
 * @memberOf OpenAjax.a11y.information
 *
 * @desc Constructor for an object that contains information about the total 
 *       number of elements and specific element types on a web page
 *
 * @param  {Object}  dom_cache    - Cache of the document object model of the web page 
 * @param  {Number}  option       - 0: All elements<br/>
 *                                  1: AT visible elements<br/>
 *                                  2: Hidden elements<br/>
 *
 * @property  {Number}  total       - Total number of elements 
 * @property  {Number}  audio       - Number of audio elements
 * @property  {Number}  controls    - Number of form controls
 * @property  {Number}  headings    - Number of heading elements (h1-h6)
 * @property  {Number}  iframes     - Number of iframes 
 * @property  {Number}  images      - Number of images (img)
 * @property  {Number}  landmarks   - Number of landmarks 
 * @property  {Number}  links       - Number of links (i.e. a and area)
 * @property  {Number}  lists       - Number of lists (i.e. ul, ol and dl)
 * @property  {Number}  listitems   - Number of listitems (i.e. il, dt and dd)
 * @property  {Number}  other       - Number of elements not in one of the 
 *                                    other counts
 * @property  {Number}  tables      - Number of table elements
 * @property  {Number}  table_cells - Number of table cells
 * @property  {Number}  objects     - Number of object, embed or applet elements
 * @property  {Number}  video       - Number of video elements
 * @property  {Number}  widgets     - Number of ARIA identified widgets
 */

OpenAjax.a11y.info.ElementSummary = function (dom_cache, option) {

  function getCount(list) {
  
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;

    var count = 0;

    if (option === 0) {
      return list.length;
    }
    else {
    
      var list_len = list.length;
  
      for (var i = 0; i < list_len; i++) {
        var de = list[i].dom_element;
        if (!de) de = list[i];
        if (!de) continue;
        
        var cs = de.computed_style;
        
        if (cs) {
          if ((cs.is_visible_to_at === VISIBILITY.VISIBLE) &&
              (option === 1)) count++;
          else if ((cs.is_visible_to_at === VISIBILITY.HIDDEN) &&
              (option === 2)) count++;
        }      
      }       
    }
   
    return count;
  }

  var total       = 0;  
  
  var audio       = 0;
  var controls    = 0;
  var events      = 0;
  var headings    = 0;
  var iframes     = 0;
  var images      = 0;
  var landmarks   = 0;
  var links       = 0;
  var lists       = 0;
  var listitems   = 0;
  var tables      = 0;
  var objects     = 0;
  var video       = 0;
  var widgets     = 0;

  if (dom_cache) {
  
    if (typeof option !== 'number') option = 0; 
  
    audio   = getCount(dom_cache.media_cache.audio_elements);
    video   = getCount(dom_cache.media_cache.video_elements);
    objects = getCount(dom_cache.media_cache.object_elements);

    controls  = getCount(dom_cache.controls_cache.control_elements);
    widgets   = getCount(dom_cache.controls_cache.widget_elements);
    events    = getCount(dom_cache.controls_cache.elements_with_events);
  
    headings  = getCount(dom_cache.headings_landmarks_cache.heading_elements);
    landmarks = getCount(dom_cache.headings_landmarks_cache.landmark_elements);

    iframes   = getCount(dom_cache.headings_landmarks_cache.iframe_elements);
  
    images    = getCount(dom_cache.images_cache.image_elements);

    links     = getCount(dom_cache.links_cache.link_elements);

    lists     = getCount(dom_cache.lists_cache.container_elements);
    listitems = getCount(dom_cache.lists_cache.listitem_elements);

    tables    = getCount(dom_cache.tables_cache.table_elements);

    total     = getCount(dom_cache.element_cache.dom_elements);

  }

  return {
     get audio         () { return audio;       },
     get elements_with_events ()  { return events;  },
     get form_controls () { return controls;    },
     get headings      () { return headings;    },
     get iframes       () { return iframes;     },
     get images        () { return images;      },
     get landmarks     () { return landmarks;   },
     get links         () { return links;       },
     get lists         () { return lists;       },
     get listitems     () { return listitems;   },
     get objects       () { return objects;     },
     get tables        () { return tables;      },
     get total         () { return total;       },
     get video         () { return video;       },
     get widgets       () { return widgets;     }
  };

};

/* ---------------------------------------------------------------- */
/*                             PageInfo                   */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor PageInfo
 *
 * @memberOf OpenAjax.a11y.information
 *
 * @desc Constructor for an object that contains a title, url and description
 *       for a group of rule results
 *
 * @param  {Object}  dom_cache    - Cache of the document object model of the web page 
 *
 * @property  {String}  title        - A short description that can be used as 
 *                                     a title or the text of a link 
 * @property  {String}  url          - A url to more information on the item   
 *
 * @property  {ElementSummary}  elements         - Summary information about all 
 *                                                 the elements on the web page
 *
 * @property  {ElementSummary}  visible_elements - Summary information about the 
 *                                                 elements visible to assistive 
 *                                                 technology on the web page
 *
 * @property  {ElementSummary}  hidden_elements - Summary information about the 
 *                                                 elements hidden from assistive 
 *                                                 technology on the web page
 */

OpenAjax.a11y.info.PageInfo = function (dom_cache) {

  var title   = dom_cache.title;
  var url     = dom_cache.url;
  var lang    = "";

  var elements = new OpenAjax.a11y.info.ElementSummary(dom_cache, 0);
  var visible  = new OpenAjax.a11y.info.ElementSummary(dom_cache, 1);
  var hidden   = new OpenAjax.a11y.info.ElementSummary(dom_cache, 2);

  return {
     get title             () { return title;    },
     get url               () { return url;      },
     get language          () { return lang;     },
     get elements          () { return elements;  },
     get visible_elements  () { return visible;  },
     get hidden_elements   () { return hidden;   },
     toString : function   () { return title;    }
  };
  
};




/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                             Rule                                 */
/* ---------------------------------------------------------------- */

/**
 * @constructor Rule
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Creates and validates a rule used to evaluate an accessibility feature 
 *       of a document
 *
 * @param {Object}    rules_nls          - NLS information for rules 
 * @param {Object}    rule_item          - Object containing rule information 
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property {Object}    cache_nls           - Cache messages NLS reference for the current language  
 * @property {Object}    wcag20_nls          - WCAG 2.0 message NLS reference for the current language  
 * @property {Object}    rules_nls           - Rule message NLS reference for the current language  
 *
 * @property {String}    rule_id             - Unique id of the rule 
 * @property {String}    rule_scope          - Defines the scope of the rule:
 *                                             page or element 
 * @property {Number}    rule_category       - Rule category 
 * @property {String}    wcag_primary_id     - Id of the primary WCAG 2.0 
 *                                             success criterion
 * @property {Array}     wcag_related_ids    - Array of ids of related WCAG 2.0 
 *                                             success criteria
 * @property {String}    cache_dependency    - Which cache (i.e. element group) 
 *                                             the rule will use 
 * @property {Array}     resource_properties - What properties of a cache or dom 
 *                                             element the rules uses in the evaluation
 * @property {Array}     target_objects      - The html objects the rule evaluates 
 *                                             (NOTE: this is informative information)
 * @property {String}    language            - The lanaguage code or codes (space 
 *                                             separated) if the rule is language 
 *                                             specfic, default is empty string
 * @property {function}  validate            - Function for evaluting the rule 
 *                                             requirements using the DOM cache
 *
 * @property {Number}  principle_filter_const          - Number used as a bit 
 *                                                       maskable filter 
 * @property {Number}  guideline_filter_const          - Number used as a bit 
 *                                                       maskable filter    
 * @property {Number}  success_criterion_filter_const  - Number used as a filter
 */

OpenAjax.a11y.Rule = function (rules_nls, rule_item) {

  this.wcag20_nls = OpenAjax.a11y.all_wcag20_nls.getNLS();
  this.rules_nls  = rules_nls[OpenAjax.a11y.locale];
  
  this.rule_id             = rule_item.rule_id;   
  this.rule_scope          = rule_item.rule_scope;   
  this.rule_category       = rule_item.rule_category;   
  this.wcag_primary_id     = rule_item.wcag_primary_id;   
  this.wcag_related_ids    = rule_item.wcag_related_ids;
  this.last_updated        = rule_item.last_updated;
  this.cache_dependency    = rule_item.cache_dependency;
  this.resource_properties = rule_item.resource_properties;
  this.target_resources    = rule_item.target_resources;
  this.language_dependency = rule_item.language_dependency;
  this.validate            = rule_item.validate;
  
  var indexes = this.wcag_primary_id.split('.');

  var p  = parseInt(indexes[0], 10);
  var g  = parseInt(indexes[1], 10);
  var sc = parseInt(indexes[2], 10);
  
  // These are numberical values for fast comparisons
  // There are constants associated with these ids

  // Unique number and bit maskaable
  this.principle_filter_const = Math.pow(2,(p-1)); 
  
  // Unique number and bit maskaable
  this.guideline_filter_const = Math.pow(2, (4*p) + (g-1));    
  
  // Unique number 
  this.success_criterion_filter_const = p * 1000 + g * 100 + sc;
  
};

/**
 * @method getRuleId
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get the programmatic id that uniquely identifies the rule
 *
 * @return {String} The rule id
 */
 
OpenAjax.a11y.Rule.prototype.getRuleId = function () {

  return this.rule_id;
  
};


/**
 * @method getRuleIdNLS
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a localized human readable id for uniquely identifying the rule
 *
 * @return {String} Localized string of the rule id
 */
 
OpenAjax.a11y.Rule.prototype.getRuleIdNLS = function () {

  return this.rules_nls.rules[this.rule_id]['ID'];
  
};

/**
 * @method getRuleCategoryConstant
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get the bit maskable numeric value for the rule category
 * 
 * @return {Number}  Numeric value of the rule category
 */

OpenAjax.a11y.Rule.prototype.getRuleCategoryConstant = function () {

  return this.rule_category;
  
};

/**
 * @method getRuleCategory
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a localized title, url and description of the rule category
 * 
 * @return {RuleInfoItem}  Returns a RuleInfoItem object
 */

OpenAjax.a11y.Rule.prototype.getRuleCategory = function () {

  var cache_nls  = OpenAjax.a11y.cache_nls;

  var item = cache_nls.getRuleCategory(this.rule_category);
  
  return (new OpenAjax.a11y.info.RuleInfoItem(this.rule_category, item.title, item.url));
  
};


/**
 * @method getRuleScope
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a localized string of the rule scope (i.e. 'element' or 'page')
 *
 * @return {String} Localized string of the rule scope
 */
 
OpenAjax.a11y.Rule.prototype.getRuleScope = function () {

  if (this.rule_scope) return this.rules_nls.rule_scope[this.rule_scope];
  
  return this.rules_nls.rule_scope[OpenAjax.a11y.RULE_SCOPE.UNKOWN];
    
};

/**
 * @method isScopePage
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Test if the rule is a page level rule
 *
 * @return {Boolean} True if the rule has a scope of page, otherwise false
 */
 
OpenAjax.a11y.Rule.prototype.isScopePage = function () {

  return this.rule_scope === OpenAjax.a11y.RULE_SCOPE.PAGE;
  
};

/**
 * @method isScopeElement
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Test if the rule is a element level rule
 *
 * @return {Boolean} True if the rule has a scope of element, otherwise false
 */
 
OpenAjax.a11y.Rule.prototype.isScopeElement = function () {

  return this.rule_scope === OpenAjax.a11y.RULE_SCOPE.ELEMENT;
  
};


/**
 * @method getRuleDefinition
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Gets the definition of the rule
 *
 * @param {Boolean}  required  - True if rule is required
 * 
 * @return {String} Localized string of the rule definition based on being 
 *                  required or recommended
 */
OpenAjax.a11y.Rule.prototype.getRuleDefinition = function (required) {

  var str = this.rules_nls.rules[this.rule_id]['DEFINITION'];
  
  var message;
  
  var vstr;

  if (str) {
  
    vstr = "%s";
  
    if (str.indexOf(vstr) >= 0) {

     if (typeof required === 'boolean') {
     
      if (required) message = this.rules_nls.message_severities.MUST;
      else message = this.rules_nls.message_severities.SHOULD;
      
     }
     else {
       // If no rule type is defined assume "must"
        message = this.rules_nls.message_severities.MUST + "/" + this.rules_nls.message_severities.SHOULD;
     }
      
     str = str.replace(vstr, message);  
   }  

   return OpenAjax.a11y.util.transformElementMarkup(str);
 }
      
 return "Definition not found for rule: " + this.rule_id;
  
};

/**
 * @method getRuleSummary
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Gets the summary of the rule
 *
 * @param {Boolean}  required  - True if rule is required
 * 
 * @return {String} Localized string of the rule summary based on being 
 *                  required or recommended
 */
OpenAjax.a11y.Rule.prototype.getRuleSummary = function (required) {

  var str = this.rules_nls.rules[this.rule_id]['SUMMARY'];
  
  var message;
  
  var vstr;

  if (str) {
  
    vstr = "%s";
  
    if (str.indexOf(vstr) >= 0) {
    
      if (typeof required === 'boolean') {
     
        if (required) message = this.rules_nls.message_severities.MUST;
        else message = this.rules_nls.message_severities.SHOULD;
      
      }
      else {
        // If no rule type is defined assume "must"
        message = this.rules_nls.message_severities.MUST + "/" + this.rules_nls.message_severities.SHOULD;
      }
      
      str = str.replace(vstr, message);
      
    }  
    return OpenAjax.a11y.util.transformElementMarkup(str);
  }
      
  return "Summary not found for rule: " + this.rule_id;
  
};

/**
 * @method getPurpose
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Gets an array strings representing the purpose, basically
 *       how does the rule help people with disabilities
 *
 * @return  {Array}  Returns an array of localized string describing the purpose
 */
OpenAjax.a11y.Rule.prototype.getPurpose = function () {

  var list = this.rules_nls.rules[this.rule_id]['PURPOSE'];
  
  var new_list = [];

  if (list && list.length) { 
  
    for (var i = 0; i < list.length; i++) {

      new_list.push(OpenAjax.a11y.util.transformElementMarkup(list[i]));
    
    } // end for
  
    return new_list;
  }  
  
  return [];
  
};

/**
 * @method getTargetResourcesDescription
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a description of the markup or page feature the rule is evaluates
 *
 * @return  {String}  Localized string representing the markup or page feature 
 *                    tested by the rule
 */
OpenAjax.a11y.Rule.prototype.getTargetResourcesDescription = function () {

  var target = this.rules_nls.rules[this.rule_id]['TARGET_RESOURCES_DESC'];
  
  if (target) return OpenAjax.a11y.util.transformElementMarkup(target);
  
  return "** Target resource description not defined";
  
};

/**
 * @method getTargetResources
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Returns an localized array strings representing target resources of 
 *       the rule
 *
 * @return  {Array}  Returns an array of strings identifying the elements and/or
 *                    attributes that the rule evaluates
 */
OpenAjax.a11y.Rule.prototype.getTargetResources = function () {
  
  if (this.target_resources) return this.target_resources;
  
  return [];
  
};

/**
 * @method getTargetResourceProperties
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get the attributes and calculated properties of element used to evaluate a rule
 *
 * @return  {Array}  Returns an array of strings identifying the calculated properties 
 *                   and/or attributes that the rule uses to evaluate the rule requirements
 */
OpenAjax.a11y.Rule.prototype.getTargetResourceProperties = function () {
  
  if (this.resource_properties) return this.resource_properties;
  
  return [];
  
};

/**
 * @method getTechniques
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get the techniques to implement the requirements of the rule 
 *
 * @return  {Array}  Array of RuleInfoItem objects
 */
OpenAjax.a11y.Rule.prototype.getTechniques = function () {

  var list = this.rules_nls.rules[this.rule_id]['TECHNIQUES'];
  
  var new_list = [];

  if (list && list.length) { 
  
    for (var i = 0; i < list.length; i++) {
    
      var item = list[i];
    
      var ref;
    
      if (typeof item === 'string') ref = new OpenAjax.a11y.info.RuleInfoItem(OpenAjax.a11y.REFERENCES.TECHNIQUE, item, "");
      else ref = new OpenAjax.a11y.info.RuleInfoItem(OpenAjax.a11y.REFERENCES.TECHNIQUE, item.title, item.url);

      new_list.push(ref);
    
    } // end for
  
    return new_list;
  }    
  
  return [];
  
};

/**
 * @method getManualCheckProcedures
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Gets manual checking proceedures for evaluating the rule
 *       requirements
 *
 * @return  {Array}  Array of RuleInfoItem objects
 */
 
OpenAjax.a11y.Rule.prototype.getManualCheckProcedures = function () {

  var list = this.rules_nls.rules[this.rule_id]['MANUAL_CHECKS'];
  
  var new_list = [];

  if (list && list.length) { 
  
    for (var i = 0; i < list.length; i++) {

      var item = list[i];
    
      var ref;
    
      if (typeof item === 'string') ref = new OpenAjax.a11y.info.RuleInfoItem(OpenAjax.a11y.REFERENCES.MANUAL_CHECK, item, "");
      else ref = new OpenAjax.a11y.info.RuleInfoItem(OpenAjax.a11y.REFERENCES.MANUAL_CHECK, item.title, item.url);

      new_list.push(ref);
    
    } // end for
  
    return new_list;
  }    
  
  return [];
  
};



/**
 * @method getInformationalLinks
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get information links related to understanding or implementation of the rule
 *
 * @return  {Array}  Returns an array of RuleInfoItem objects
 *
 * @example
 *
 * var node_list = [];
 * var info_links = rule.getInformationalLinks();
 * 
 * for(var i = 0; i < info_links.length; i++) {
 *   var info_link = info_links[i];
 *
 *   // Using object properties to create a link element
 *   var node = document.createElement('a');
 *   node.appendChild(document.createTextNode(info_link.title));
 *   node.setAttribute('href',  info_link.url);
 *   node.setAttribute('class', info_link.type_const.toString());
 *
 *   node_list.push(node);
 * }
 */
 
OpenAjax.a11y.Rule.prototype.getInformationalLinks = function () {

  var list = this.rules_nls.rules[this.rule_id]['INFORMATIONAL_LINKS'];
  
  var new_list = [];

  if (list && list.length) { 
  
    for (var i = 0; i < list.length; i++) {

      var link = list[i];
      
      var ref = new OpenAjax.a11y.info.RuleInfoItem(link.type, link.title, link.url);

      new_list.push(ref);
    
    } // end for
  
    return new_list;
  }  
  
  return [];
  
};



/**
 * @method getPrimarySuccessCriterion
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get information about primary WCAG 2.0 Success Criteria for the rule
 *
 * @return  {SuccessCriterionInfo}  Object representing information about the SC
 *
 * @example
 *
 * var sc_info = rule.getPrimarySuccessCriterion();
 * 
 * // Creating a link element to the primary success criterion
 * var node = document.createElement('a');
 * node.appendChild(document.createTextNode(sc_info.title));
 * node.setAttribute('href',  sc_info.url);
 * node.setAttribute('class', sc_info.style);
 * } 
 */

OpenAjax.a11y.Rule.prototype.getPrimarySuccessCriterion = function () {

  var ref = new OpenAjax.a11y.info.SuccessCriterionInfo(this.wcag_primary_id);

  return ref;

};

/**
 * @method getRelatedSuccessCriteria
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get information about the related WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Array}  Array of SuccessCriterionInfo objects 
 */

OpenAjax.a11y.Rule.prototype.getRelatedSuccessCriteria = function () {

  var list = [];

  var ids = this.wcag_related_ids;
  var ids_len = ids.length;

  for (var i = 0; i < ids_len; i++) {
  
    var id = ids[i];
  
    var ref = new OpenAjax.a11y.info.SuccessCriterionInfo(id);

    list.push(ref);
  }

  return list;
};

/**
 * @method getWCAG20LevelConstant
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get the numerical constant for the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {Number}  Number representing the WCAG 2.0 level 
 */

OpenAjax.a11y.Rule.prototype.getWCAG20LevelConstant = function () {

  var sc = this.wcag20_nls.getNLSItemById(this.wcag_primary_id);

  return sc.level;

};

/**
 * @method getWCAG20Level
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get the string representation of the the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {String}  String representing the WCAG 2.0 success criterion level 
 *                    (i.e. A, AA or AAA)
 */

OpenAjax.a11y.Rule.prototype.getWCAG20Level = function () {

  return this.getPrimarySuccessCriterion().level;

};

/**
 * @method getPrincipleFilterConstant
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a bit maskable filter constant that represents the WCAG 2.0 Principle
 *
 * @return  {Number}  Number representing the WCAG 2.0 Principle
 */

OpenAjax.a11y.Rule.prototype.getPrincipleFilterConstant = function () {

   return this.principle_filter_const;
   
};

/**
 * @method getGuidelineFilterConstant
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a bit maskable filter constant that represents the WCAG 2.0 Guideline 
 *
 * @return  {Number}  Number representing the WCAG 2.0 Guideline
 */

OpenAjax.a11y.Rule.prototype.getGuidelineFilterConstant = function () {

   return this.guideline_filter_const;
   
};

/**
 * @method getSuccessCriterionFilterConstant
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Get a filter constant that represents the WCAG 2.0 Success Criterion 
 *
 * @return  {Number}  Number representing the WCAG 2.0 Success Criterion
 */

OpenAjax.a11y.Rule.prototype.getSuccessCriterionFilterConstant = function () {

   return this.success_criterion_filter_const;
   
};



/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.Rule
 *
 * @desc Returns a JSON representation of the rule
 *
 * @param  {String}   prefix    - String of leading spaces for formatting JSON output (Optional) 
 * @param  {Boolean}  required  - Required is needed for adjusting definition and summary strings to ruleset 
 *                                requirements 
 *
 * @return  {String}  Returns a JSON representation of the rule
 */

OpenAjax.a11y.Rule.prototype.toJSON = function (prefix, required) {

  function stringItem(property, value, last) {
    if (typeof value === 'string') json += prefix + "    \"" + property + "\" : \"" + OpenAjax.a11y.util.escapeForJSON(value)  + "\"";    
    else json += prefix + "    \"" + property + "\" : \"\"";
    
    if (last) json += "\n";
    else json += ",\n";
  }

  function numberItem(property, value, last) {
    json += prefix + "    \"" + property + "\" : " + value;    
    
    if (last) json += "\n";
    else json += ",\n";
  }

  function booleanItem(property, value, last) {
    if (value) json += prefix + "    \"" + property + "\" : true";    
    else json += prefix + "    \"" + property + "\" : false";    
    
    if (last) json += "\n";
    else json += ",\n";
  }

  function stringListItem(property, list, last) {
    json += prefix + "    \"" + property + "\" : [";
    
    if ((typeof list === 'object') && list.length) {    
      var last_item = list.length - 1;    
      for (var i = 0; i < list.length; i++) {
        if (last_item === i) json += "\"" + OpenAjax.a11y.util.escapeForJSON(list[i]) + "\"";
        else json += "\"" + OpenAjax.a11y.util.escapeForJSON(list[i]) + "\",";
      }
    }
    
    if (last) json += "]\n";
    else json += "],\n";
  }

  function stringListItem2(property, list, last ) {
    json += prefix + "    \"" + property + "\" : [";
  
    if ((typeof list === 'object') && list.length) {    
      var last_item = list.length - 1;    
      for (var i = 0; i < list.length; i++) {
        if (last_item === i) json += "  \"" + OpenAjax.a11y.util.escapeForJSON(list[i]) + "\"\n";
        else json += "  \"" + OpenAjax.a11y.util.escapeForJSON(list[i]) + "\",\n";
      }
    }

    if (last) json += "]\n";
    else json += "],\n";

  }


  function addListOfStrings(name, list, last) {

    json += prefix + "    \"" + name + "\" : [\n";
        
    if ((typeof list === 'object') && list.length) {    
      var last_item = list.length - 1;    
      for (var i = 0; i < list.length; i++) {
        if (last_item === i) json += "          \"" + OpenAjax.a11y.util.escapeForJSON(list[i]) + "\"\n";
        else json += "           \"" + OpenAjax.a11y.util.escapeForJSON(list[i]) + "\",\n";
      }
    }

    if (last) json += prefix + "    ]\n";
    else json += prefix + "    ],\n";

  }



  function addInformationalLinks(last) {
  
    function addReferenceItem(reference, last) {

      json += prefix + "      { \"type\"  : "   + reference['type']  + ",\n";
      json += prefix + "        \"title\" : \"" + OpenAjax.a11y.util.escapeForJSON(reference['title']) + "\",\n";
      json += prefix + "        \"url\"   : \"" + OpenAjax.a11y.util.escapeForJSON(reference['url'])   + "\"\n";
    
      if (last) json += prefix + "      }\n";
      else json += prefix + "      },\n";
    
    }
  
    json += prefix + "    \"informational_links\" : [\n";
    
    var info_links = nls_rule['INFORMATIONAL_LINKS'];
    
    if ((typeof info_links === 'object') && info_links.length) {    
      var last_item = info_links.length - 1;    
      for (var i = 0; i < info_links.length; i++) {
        if (last_item === i) addReferenceItem(info_links[i], true);
        else addReferenceItem(info_links[i], false);
      }
    }

    if (last) json += prefix + "    ]\n";
    else json += prefix + "    ],\n";

  }


  if (typeof prefix !== 'string') prefix = "";

  var json = "";

  var rule     = this;
  var nls_rule = this.rules_nls.rules[this.rule_id];
 
  json += prefix + "  {\n";

  stringItem(    'rule_id',             this.rule_id);
  numberItem(    'scope',               this.rule_scope);  
  stringItem(    'wcag_primary',        this.wcag_primary_id);  
  stringListItem('wcag_related',        this.wcag_related_ids);  
  stringItem(    'last_updated',        this.last_updated);
  stringListItem('target_resources',    this.target_resources); 
  numberItem(    'rule_category',       this.rule_category);
  stringItem(    'language_dependency', this.language_dependency);
  stringItem(    'cache_dependency',    this.cache_dependency);
  stringListItem('mc_properties',       this.mc_properties);
  stringListItem('resource_properties', this.resource_properties); 
  stringItem(    'validate',            this.validate.toString());    
  
  stringItem('nls_rule_id', nls_rule['ID']);
  
  if (typeof required === 'boolean') {
    stringItem('definition', this.getRuleDefinition(required));
    stringItem('summary', this.getRuleSummary(required));
  }
  else {
    stringItem('definition', nls_rule['DEFINITION']);
    stringItem('summary', nls_rule['SUMMARY']);
  }
  
  stringItem('target_resource_desc', nls_rule['TARGET_RESOURCES_DESC']);

  addListOfStrings('purpose',       nls_rule['PURPOSE']);

  addListOfStrings('techniques',    nls_rule['TECHNIQUES']);

  addListOfStrings('manual_checks', nls_rule['MANUAL_CHECKS']);
  
  addInformationalLinks();
  
  stringItem('rule_result_action_singular',        nls_rule['RULE_RESULT_MESSAGES']['ACTION_FAIL_S']);    
  stringItem('rule_result_action_plural',          nls_rule['RULE_RESULT_MESSAGES']['ACTION_FAIL_P']);    
  stringItem('rule_result_manual_checks_singular', nls_rule['RULE_RESULT_MESSAGES']['ACTION_MC_S']);    
  stringItem('rule_result_manual_checks_plural',   nls_rule['RULE_RESULT_MESSAGES']['ACTION_MC_P']);    
  stringItem('rule_result_not_applicable',         nls_rule['RULE_RESULT_MESSAGES']['NOT_APPLICABLE']);    

  stringItem('node_result_pass_1', nls_rule['NODE_RESULT_MESSAGES']['PASS_1']);
  stringItem('node_result_pass_2', nls_rule['NODE_RESULT_MESSAGES']['PASS_2']);    
  stringItem('node_result_pass_3', nls_rule['NODE_RESULT_MESSAGES']['PASS_3']);    

  stringItem('node_result_corrective_action_1', nls_rule['NODE_RESULT_MESSAGES']['ACTION_1']);
  stringItem('node_result_corrective_action_2', nls_rule['NODE_RESULT_MESSAGES']['ACTION_2']);
  stringItem('node_result_corrective_action_3', nls_rule['NODE_RESULT_MESSAGES']['ACTION_3']);
  stringItem('node_result_corrective_action_4', nls_rule['NODE_RESULT_MESSAGES']['ACTION_4']);

  stringItem('node_result_manual_check_1', nls_rule['NODE_RESULT_MESSAGES']['MANUAL_CHECK_1']);  
  stringItem('node_result_manual_check_2', nls_rule['NODE_RESULT_MESSAGES']['MANUAL_CHECK_2']);
  stringItem('node_result_manual_check_3', nls_rule['NODE_RESULT_MESSAGES']['MANUAL_CHECK_3']);

  stringItem('node_result_hidden_1', nls_rule['NODE_RESULT_MESSAGES']['HIDDEN_1']);
  stringItem('node_result_hidden_2', nls_rule['NODE_RESULT_MESSAGES']['HIDDEN_2'], true);
  
  json += prefix + "  }";
 
  return json;

};


/* ---------------------------------------------------------------- */
/*                             Rules                                */
/* ---------------------------------------------------------------- */

/**
 * @constructor Rules
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Creates an array of rule objects for evaluating accessibility
 */

OpenAjax.a11y.Rules = function () {

  this.rules = [];

  this.rules_nls   = {};
  
};

/**
 * @method addRule
 *
 * @memberOf OpenAjax.a11y.Rules
 *
 * @desc Adds a new rule to the list of rules
 *
 * @param  {Object}    rule_item          - Object containing rule information 
 *
 * @return  {Boolean} Returns true if the rule was added successfully; false if there was an error
 */

OpenAjax.a11y.Rules.prototype.addRule = function (rule_item) {

  var errors = false;

  if (typeof rule_item.rule_id !== 'string') {
    OpenAjax.a11y.logger.error("  ** Rule ID is missing");
    errors = true;
  }  

  if (this.getRuleByRuleId(rule_item.rule_id)) {
    OpenAjax.a11y.logger.error("  ** Duplicate Rule ID: " + rule_item.rule_id);
    errors = true;
  }  
  
  if (typeof rule_item.wcag_primary_id !== 'string') {
    OpenAjax.a11y.logger.error("  ** Rule " + rule_item.rule_id + " primary wcag id is missing"); 
    errors = true;
  }  
  
  if (typeof rule_item.wcag_related_ids !== 'object') {
    OpenAjax.a11y.logger.error("  ** Rule " + rule_item.rule_id + " related wcag ids is missing"); 
    errors = true;
  }  
  
  if (!this.validCacheDependency(rule_item.cache_dependency)) {
    OpenAjax.a11y.logger.error("  ** Rule " + rule_item.rule_id + " has invalid or missing cache dependency property"); 
    errors = true;
  }  

  if (typeof rule_item.resource_properties !== 'object') {
    OpenAjax.a11y.logger.error("  ** Rule " + rule_item.rule_id + " resource properties is missing or not an array"); 
    errors = true;
  }  
  
  if (typeof rule_item.language_dependency !== 'string') {
    OpenAjax.a11y.logger.error("  ** Rule " + rule_item.rule_id + " language property is missing or not a string"); 
    errors = true;
  }  
  
  if (typeof rule_item.validate !== 'function') {
    OpenAjax.a11y.logger.error("  ** Rule " + rule_item.rule_id + " validate property is missing or not a function"); 
    errors = true;
  }  

  if (errors) return false;
  
  var oaa_rule = new OpenAjax.a11y.Rule(this.rules_nls, rule_item);

  this.rules.push(oaa_rule);

  return true;

};

/**
 * @method addRulesFromJSON
 *
 * @memberOf OpenAjax.a11y.Rules
 *
 * @desc Adds a rules from a list of rules in JSON format
 *
 * @param {Object}    rule_array  - An array of objects representing OAA rules 
 *
 * @return  {Boolean} Returns true if the rules were added successfully; false if there was an error
 */

OpenAjax.a11y.Rules.prototype.addRulesFromJSON = function (rule_array) {

  var rule_item;

//  OpenAjax.a11y.logger.debug(" ---- Adding OAA Rules ---- ");

  for (var i = 0; i < rule_array.length; i++) {

    rule_item = rule_array[i];
    
    OpenAjax.a11y.logger.debug("  RULE LOADING: " + rule_item.rule_id);
//    OpenAjax.a11y.logger.debug("  last update: " + rule_item.last_updated);
//    OpenAjax.a11y.logger.debug("   dependency: " + rule_item.cache_dependency);
//    OpenAjax.a11y.logger.debug("   properties: " + typeof rule_item.resource_properties);
//    OpenAjax.a11y.logger.debug("     language: " + rule_item.language_dependency);
//    OpenAjax.a11y.logger.debug("     validate: " + typeof rule_item.validate);

    this.addRule(rule_item);

  }

};

/**
 * @method addRulesNLSFromJSON
 *
 * @memberOf OpenAjax.a11y.Rules
 *
 * @desc Adds a rule NLS 
 *
 * @param {Object}    rule_array  - An array of objects representing OAA rules 
 *
 * @return  {Boolean} Returns true if the rules were added successfully; false if there was an error
 */

OpenAjax.a11y.Rules.prototype.addRulesNLSFromJSON = function (locale, rules_nls) {

  this.rules_nls[locale] = rules_nls;

};

/**
 * @method getRuleByRuleId
 *
 * @memberOf OpenAjax.a11y.Rules
 *
 * @desc Returned rule object with the id 
 *
 * @param  {String}  rule_id   - id of the rule to find 
 *
 * @return  {Rule} Returns rule object if the rule id is found; null if the rule id is not found 
 */

OpenAjax.a11y.Rules.prototype.getRuleByRuleId = function (rule_id) {

  var rule;
  var max = this.rules.length;

  for (var i = 0; i < max; i++ ) {
    rule = this.rules[i];
    if (rule.rule_id === rule_id) return rule;
  }

  return null;
};

/**
 * @method validCacheDependency
 *
 * @memberOf OpenAjax.a11y.Rules
 *
 * @desc Checks to see if the cache reference is valid 
 *
 * @param {String}    cache_name   - Property name of the cache 
 *
 * @return  {Boolean} Returns true if the cache dependency is valid; otherwise false
 */

OpenAjax.a11y.Rules.prototype.validCacheDependency = function (cache_name) {

  var CACHE_NAMES = OpenAjax.a11y.CACHE_NAMES;
  
  var max = CACHE_NAMES.length;
  
  for (var i = 0; i < max; i++) {
    if (cache_name === CACHE_NAMES[i]) return true;
  } // end loop
                    
  return false;
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.Rules
 *
 * @desc Exports current rule information to a JSON format 
 *
 * @return {String}  JSON formatted string
 */

OpenAjax.a11y.Rules.prototype.toJSON = function (prefix) {

  if (typeof prefix !== 'string') prefix = "";

  var json = "";

  json += prefix + "[\n";

  var last = this.rules.length - 1;
  for (var i = 0; i < this.rules.length; i++ ) {
  
    var rule = this.rules[i];
  
    if (i === last) json += rule.toJSON(prefix)  + "\n";
    else json += rule.toJSON(prefix) + ",\n";
    
  }

  json += prefix + "]\n";

  return json;

};
  /*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                             ResultSummary                        */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor ResultSummary
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructor for an object that contains summary of node results for rule 
 *       result, cache item result, rule result group result or evaluation result 
 *       objects
 *                                      check 
 * @property  {Number}  percent_passed   - Percentage of node results that pass 
 *                                      the rule (0 <= value <= 100)   
 * @property  {Number}  passed        - Number of node results that passed the 
 *                                      rule (value >= 0)
 * @property  {Number}  page          - Number of node results that contribute to 
 *                                      a page level manual check
 * @property  {Number}  violations    - Number of node results that failed the 
 *                                      rule as a violation (value >= 0) 
 * @property  {Number}  warnings      - Number of node results that failed the 
 *                                      rule as a warning (value >= 0) 
 * @property  {Number}  failures      - Number of node results that failed the 
 *                                      rule as a warning or violation (value >= 0)  
 * @property  {Number}  manual_checks - Number of node results that require a 
 *                                      manual check (value >= 0)  
 * @property  {Number}  hidden        - Number of node results that are hidden
 *                                      (value >= 0)
 * @property  {Number}  total         - Total number of node results that 
 *                                      passed and failed (value >= 0)
 *                                      (note: total count does not include manual 
 *                                             checks)
 */
 
OpenAjax.a11y.ResultSummary = function () {

  /**
   * @function calcSummary
   * @private
   *
   * @desc Calculates the percentage of passed node results 
   *
   * @param  {Number}  Number of node results that passed
   */

  function calcSummary() {
    if (t === 0) return;
    pp = Math.round((100*p)/t);
    if ((pp > 99) && (f > 0)) pp = 99;
  }

  var has_results     = false;
  
  var pp = 0;  
  var p  = 0;  
  var pg = 0;
  var v  = 0;
  var w  = 0;  
  var mc = 0;    
  var h  = 0;    
  var f  = 0;  
  var t  = 0;  

  return { 
     get percent_passed() { return pp; },
     get passed()         { return p;  },
     get page()           { return pg; },
     get violations()     { return v;  },
     get warnings()       { return w;  },
     get manual_checks()  { return mc; },
     get hidden()         { return h;  },
     get failures ()      { return f;  },
     get total ()         { return t;  },

    /**
     * @method addPassed
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     * @private
     *
     * @desc Adds passed node results to the summary calculation 
     *
     * @param  {Number}  n  - Number of node results that passed
     */

     addPassed : function(n) {

       if (n > 0) {
         has_results = true;
         p += n;
         t  += n;
         calcSummary();
       }   
     },


    /**
     * @method addViolations
     * @private
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Adds violation node results to the summary calculation 
     *
     * @param  {Number}  n  - Number of node results that passed
     */

    addViolations : function(n) {

      if (n > 0) {
        has_results = true;
        v += n;
        f += n;
        t += n;
        calcSummary();
      }  
    },

    /**
     * @method addWarnings
     * @private
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Adds warning node results to the summary calculation 
     *
     * @param  {Number}  n  - Number of node results that passed
     */

    addWarnings : function(n) {
    
      if (n > 0) {
        has_results = true;
        w += n;
        f += n;
        t += n;
        calcSummary();
      }  
    },


    /**
     * @method addManualChecks
     * @private
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Adds manual check node results to the summary calculation 
     *
     * @param  {Number}  n  - Number of node results that passed
     */

    addManualChecks : function(n) {
      if ( n > 0) {
        has_results = true;
        mc += n;
      }  
    },


    /**
     * @method addPage
     * @private
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Adds page node results to the summary calculation 
     *
     * @param  {Number}  n  -  Number of node results that are page
     */

    addPage : function(n) {
      if (n > 0) pg += n;
    },

    /**
     * @method addHidden
     * @private
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Adds hidden node results to the summary calculation 
     *
     * @param  {Number}  n  -  Number of node results that are hidden
     */

    addHidden : function(n) {
      if (n > 0) h += n;
    },


    /**
     * @method addResultSummary
     * @private
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Adds a result summary to the summary calculation 
     *
     * @param  {ResultSummary}  rs Number of node results that passed
     */

    addResultSummary : function(rs) {
    
       p  += rs.passed;
       t  += rs.passed;
       v  += rs.violations;
       t  += rs.violations;
       f  += rs.violations;
       w  += rs.warnings;
       t  += rs.warnings;
       f  += rs.warnings;
       mc += rs.manual_checks;
       pg += rs.pg;
       h  += rs.hidden;

       if (t) calcSummary();
       if (t || mc) has_results = true;
       
    },

    /**
     * @method hasResults
     *
     * @memberOf OpenAjax.a11y.ResultSummary
     *
     * @desc Tests the summary results for any node results
     *
     * @return  {Boolean}  If true the summary results have at least one node 
     *                     results, otherwise false
     */
  
    hasResults : function(n) {
      return has_results;
    }
  };
};




/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                       EvaluationResult                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor EvaluationResult
 *
 * @memberOf OpenAjax.a11y
 *
 * @param  {Object}  ruleset                    - Ruleset object used to generate 
 * @param  {String}  title      -  Title of the web page being evaluated 
 * @param  {String}  url        -  URL of the web page being evaluated 
 * @param  {Object}  dom        -  DOM of the web page being evaluated
 * @param  {Object}  dom_cache  -  DOMCache object used for evaluation
 *
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property  {Object}  ruleset            - Ruleset object used to generate 
 *                                           the evaluation
 * @property  {String}  ruleset_id         - ID of the ruleset
 * @property  {String}  ruleset_title      - Title of the ruleset
 * @property  {String}  ruleset_version    - Version of the ruleset
 * @property  {Number}  evaluation_levels  - WCAG 2.0 evaluations levels used 
 *                                           for evaluation
 *
 * @property  {Boolean} recommended_rules_enabled  - Boolean indicating if recommended 
 *                                                   rules were evaluated
 *
 * @property {String} title  - The title of the document evaluated
 * @property {String} url    - The url of the ldocument evaluated
 * @property {String} date   - Date of the evaluation
 * @property {String} time   - Time of day the document was evaluated
 *
 * @property {Object} doc        - Reference to browser document object model 
 *                                 (DOM) that holds the document to be analyzed
 * @property {Object} dom_cache  - Reference to DOMCache object
 *
 * @property {Array}  rule_results  - Array of rule result objects 
 *
 * @example
 *
 * var url   = window.location.href;
 * var title = window.title;
 * var doc   = window.document;
 *
 * var ruleset            = OpenAjax.a11y.all_rulesets.getRuleset('WCAG20_TRANS');
 * var evaluation_result  = ruleset.evaluate(url, title, doc, null, true);
 */
 
OpenAjax.a11y.EvaluationResult = function (ruleset, title, url, doc, dom_cache) {

  this.ruleset = ruleset;
 
  this.ruleset_id                = ruleset.getRulesetId();
  this.ruleset_title             = ruleset.getRulesetTitle();
  this.ruleset_version           = ruleset.getRulesetVersion();
  this.evaluation_levels         = ruleset.getEvaluationLevelsConstant();
  this.recommended_rules_enabled = ruleset.getRecommendedRulesEnabled();
  
  this.ruleset_description  = ruleset.ruleset_description;
  this.ruleset_author_name  = ruleset.ruleset_author_name;
  this.ruleset_author_url   = ruleset.ruleset_author_url;
  this.required_count       = ruleset.required_count;
  this.recommended_count    = ruleset.recommended_count;
  this.number_of_rules      = ruleset.number_of_rules;
  
  this.title = title;
  this.url   = url;
  
  this.date = OpenAjax.a11y.util.getFormattedDate();
  
  this.doc       = doc;
  this.dom_cache = dom_cache;        
  
  this.rule_results = [];
  
  this.result_summary = new OpenAjax.a11y.ResultSummary();
  
  this.web_page_information =  new OpenAjax.a11y.info.PageInfo(dom_cache);
  
};

/**
 * @method getDocumentObject
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Get the document object evaluated
 *
 * @return {Object}  Document object
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getDocumentObject = function () {
  return this.doc;
};


/**
 * @method getRulesetInfo
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return ruleset of information 
 *
 * @return {RulesetInfo}  RulesetInfo object
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetInfo = function () {
  return this.ruleset.getRulesetInfo();
};

/**
 * @method getPageInfo
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return information on the web page evaluated 
 *
 * @return {PageInfo}  PageInfo object
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getPageInfo = function () {
  return this.web_page_information;
};


/**
 * @method getTitle
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Get the title of the evaluated document
 *
 * @return {String}  String representing the title
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getTitle = function () {
  return this.title;
};

/**
 * @method getURL
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Get the url of the evaluated document
 *
 * @return {String}  String representing the title
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getURL = function () {
  return this.url;
};

/**
 * @method getDate
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Get the date the document
 *
 * @return {String}  String representing the title
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getDate = function () {
  return this.date;
};




/**
 * @method getRulesetId
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return id of the ruleset 
 *
 * @return {String}  String representing the ruleset ID
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetId = function () {
  return this.ruleset_id;
};

/**
 * @method getRulesetTitle
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return title of the ruleset
 *
 * @return {String}  String representing the NLS title
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetTitle = function () {
  return this.ruleset_title;
};

/**
 * @method getRulesetVersion
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return version of the ruleset
 *
 * @return {String}  String representing the version
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetVersion = function () {
  return this.ruleset_version;
};

/**
 * @method getRulesetDescription
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return the description of the ruleset
 *
 * @return {String}  String representing the version
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetDescription = function () {
  return this.ruleset_description;
};

/**
 * @method getRulesetAuthor
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return the author of the ruleset
 *
 * @return {String}  String representing the author
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetAuthor = function () {
  return this.ruleset_author_name;
};

/**
 * @method getRulesetAuthorURL
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return the author url of the ruleset
 *
 * @return {String}  String representing the author url
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getRulesetAuthorURL = function () {
  return this.ruleset_author_url;
};


/**
 * @method getNumberOfRequiredRules
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return number of required rules
 *
 * @return {Number}  Number of required rules in the ruleset
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getNumberOfRequiredRules = function () {
  return this.required_count;
};

/**
 * @method getNumberOfRecommendedRules
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return number of recommended rules
 *
 * @return {Number}  Number of recommended rules in the ruleset
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getNumberOfRecommendedRules = function () {
  return this.recommended_count;
};

/**
 * @method getNumberOfRules
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Return total number of rules
 *
 * @return {Number}  Total number of rules in the ruleset
 */
 
OpenAjax.a11y.EvaluationResult.prototype.getNumberOfRules = function () {
  return this.number_of_rules;
};


 /**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Gets numerical summary information about the rule results 
 *
 * @return {ResultSummary} Returns the ResultSummary object 
 */
OpenAjax.a11y.EvaluationResult.prototype.getResultSummary = function () {

  return this.result_summary;

};

 /**
 * @method getRuleResultById
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Gets rule result object with the associated id
 *
 * @return {RuleResult} Returns the ResultResult object 
 */
OpenAjax.a11y.EvaluationResult.prototype.getRuleResultById = function (id) {

  for (var i = 0; i < this.rule_results.length; i++ ) {
    var rr = this.rule_results[i];

    if (rr.getRuleId() === id) return rr;
  }

  alert("No Rule Result found for: " + id);
   
  return null;

};

/**
 * @method getCacheItemsByElementType
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of cache items based on the element type and evaluation results
 *
 * @param  {Number}  element_type   - Number representing the element type
 * @param  {Number}  filter         - Number representing the evaluation results filter
 *
 * @return {FilteredCacheItemResults}  The object containing the set of cache items
 */

OpenAjax.a11y.EvaluationResult.prototype.getCacheItemsByElementType = function (element_type, filter) {

  var results = new OpenAjax.a11y.FilteredCacheItemResults(this);
  
  if (this.dom_cache) results.getCacheItemResults(element_type, filter);
  
  return results;

};


/**
 * @method getCacheItemsByRuleCategory
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of cache items based on their evaluation results and rule category
 *
 * @param  {Number}  rule_category  - Number representing the rule category
 * @param  {Number}  filter         - Number representing the evaluation results filter
 *
 * @return {FilteredCacheItemResults}  The object containing the set of cache items
 */

OpenAjax.a11y.EvaluationResult.prototype.getCacheItemsByRuleCategory = function (rule_category, filter) {

  var results = new OpenAjax.a11y.FilteredCacheItemResults(this);
  
  if (this.dom_cache) results.getCacheItemResults(rule_category, filter);
  
  return results;

};



/**
 * @method getFilteredRuleResultsByWCAG20
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules organized in a tree structure by WCAG 2.0 Principles, Guidelines and Success Criteria
 *
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByWCAG20 = function (filter) {

  var RULE_CATEGORIES = OpenAjax.a11y.RULE_CATEGORIES;

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();

  var principles;
  var principle;
  var guideline;
  var success_criterion;
  var nls_item;
  
  var evaluation_levels = this.evaluation_levels;

 // OAA_WEB_ACCESSIBILITY_LOGGING.logger.debug("IN WCAG 2.0 SUMMARY");

  principles = new OpenAjax.a11y.FilteredRuleResultsGroups(this, RULE_CATEGORIES.ALL, "WCAG 2.0 Summary");

  nls_item = nls_wcag20.getNLSItemById('1');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '1', nls_item.title, nls_item.url_spec, nls_item.description);
  
  nls_item = nls_wcag20.getNLSItemById('1.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '1.1', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('1.1.1').level & evaluation_levels) {
    nls_item = nls_wcag20.getNLSItemById('1.1.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.1.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item = nls_wcag20.getNLSItemById('1.2');
  guideline         = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '1.2', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('1.2.1').level & evaluation_levels) {
    nls_item = nls_wcag20.getNLSItemById('1.2.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.2').level & evaluation_levels) {
    nls_item = nls_wcag20.getNLSItemById('1.2.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.3').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.4').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.5').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.6').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.6');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.6', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.7').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.7');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.7', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.8').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.8');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.8', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.9').level & evaluation_levels) {  
    nls_item = nls_wcag20.getNLSItemById('1.2.9');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.9', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('1.3');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '1.3', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('1.3.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.3.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.3.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.3.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.3.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.3.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.3.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.3.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.3.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('1.4');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '1.4', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('1.4.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.3').level & evaluation_levels) {    
    nls_item  = nls_wcag20.getNLSItemById('1.4.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.4').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.5').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.6').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.6');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.6', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.7').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.7');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.7', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.8').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.8');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.8', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.9').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('1.4.9');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.9', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  principle.addFilteredRuleResultsGroup(guideline);

  principles.addFilteredRuleResultsGroup(principle);

  nls_item  = nls_wcag20.getNLSItemById('2');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '2', nls_item.title, nls_item.url_spec, nls_item.description);
  
  nls_item  = nls_wcag20.getNLSItemById('2.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '2.1', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('2.1.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.1.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.1.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.1.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.1.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.1.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.1.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.1.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.1.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('2.2');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '2.2', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('2.2.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.2.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.2.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.2.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.4').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.2.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);    
  }
  
  if (nls_wcag20.getNLSItemById('2.2.5').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.2.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('2.3');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '2.3', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('2.3.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.3.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.3.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('2.3.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.3.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.3.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('2.4');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '2.4', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('2.4.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('2.4.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.4').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.5').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.6').level & evaluation_levels) {    
    nls_item  = nls_wcag20.getNLSItemById('2.4.6');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.6', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.7').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.7');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.7', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.8').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.8');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.8', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.9').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.9');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.9', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.10').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('2.4.10');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.10', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  principles.addFilteredRuleResultsGroup(principle);

  nls_item  = nls_wcag20.getNLSItemById('3');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '3', nls_item.title, nls_item.url_spec, nls_item.description);
  
  nls_item  = nls_wcag20.getNLSItemById('3.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '3.1', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('3.1.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.1.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.1.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.1.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.1.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.4').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.1.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.5').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.1.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);    
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.6').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.1.6');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.6', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }  
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('3.2');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '3.2', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('3.2.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.2.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.2.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.2.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.4').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.2.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.5').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.2.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  nls_item  = nls_wcag20.getNLSItemById('3.3');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '3.3', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('3.3.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.3.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.3.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.3').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.3.3');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.3', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.4').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.3.4');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.4', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.5').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.3.5');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.5', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.6').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('3.3.6');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.6', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  principles.addFilteredRuleResultsGroup(principle);

  nls_item  = nls_wcag20.getNLSItemById('4');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '4', nls_item.title, nls_item.url_spec, nls_item.description);
  
  nls_item  = nls_wcag20.getNLSItemById('4.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroups(this, '4.1', nls_item.title, nls_item.url_spec, nls_item.description);
  
  if (nls_wcag20.getNLSItemById('4.1.1').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('4.1.1');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '4.1.1', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  if (nls_wcag20.getNLSItemById('4.1.2').level & evaluation_levels) {  
    nls_item  = nls_wcag20.getNLSItemById('4.1.2');
    success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '4.1.2', nls_item.title, nls_item.url_spec, nls_item.description);
    guideline.addFilteredRuleResultsGroup(success_criterion);  
  }
  
  principle.addFilteredRuleResultsGroup(guideline);

  principles.addFilteredRuleResultsGroup(principle);

  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     principles.addRuleResult(rule_result.getPrimarySuccessCriterion().id, rule_result, filter);
     
  }   
  
  return principles;


};


/**
 * @method getFilteredRuleResultsByPrinciple
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules associated with a WCAG 2.0 Principle
 *
 * @param {Number}  principle_id  -  Number representing the principle id 
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByPrinciple = function (principle_id, filter) {

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();

  var nls_pr = nls_wcag20.getNLSItemById(principle_id);

  var principle = new OpenAjax.a11y.FilteredRuleResultsGroup(this, principle_id, nls_pr.title, nls_pr.url_spec, nls_pr.description);
    
  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     var id = rule_result.getPrincipleFilterConstant();
  
     principle.addRuleResult(id, rule_result, filter);
     
  }   
  
  return principle;

};

/**
 * @method getFilteredRuleResultsByPrinciples
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules organized in a tree structure by WCAG 2.0 Principles
 *
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByPrinciples = function (filter) {

  var RULE_CATEGORIES  = OpenAjax.a11y.RULE_CATEGORIES;
  var WCAG20_PRINCIPLE = OpenAjax.a11y.WCAG20_PRINCIPLE;

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();
 
  var evaluation_levels = this.evaluation_levels;

  var principles = new OpenAjax.a11y.FilteredRuleResultsGroups(this, "wcag20_principles_summary", "WCAG 2.0 Principles");
 
  var nls_pr = nls_wcag20.getNLSItemById('1');
  var principle = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_PRINCIPLE.P_1, nls_pr.title, nls_pr.url_spec, nls_pr.description);
  principles.addFilteredRuleResultsGroup(principle);

  nls_pr = nls_wcag20.getNLSItemById('2');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_PRINCIPLE.P_2, nls_pr.title, nls_pr.url_spec, nls_pr.description);
  principles.addFilteredRuleResultsGroup(principle);  
  
  nls_pr = nls_wcag20.getNLSItemById('3');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_PRINCIPLE.P_3, nls_pr.title, nls_pr.url_spec, nls_pr.description);
  principles.addFilteredRuleResultsGroup(principle);
  
  nls_pr = nls_wcag20.getNLSItemById('4');
  principle = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_PRINCIPLE.P_4, nls_pr.title, nls_pr.url_spec, nls_pr.description);  
  principles.addFilteredRuleResultsGroup(principle);
  
  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     var id = rule_result.getPrincipleFilterConstant();
  
     principles.addRuleResult(id, rule_result, filter);
     
  }   
  
  return principles;

};


/**
 * @method getFilteredRuleResultsByGuideline
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules associated with a WCAG 2.0 Guideline
 *
 * @param {Number}  guideline_id  -  Number representing the guideline id 
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByGuideline = function (guideline_id, filter) {

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();

  var nls_gl = nls_wcag20.getNLSItemById(guideline_id);
  var guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, guideline_id, nls_gl.title, nls_gl.url_spec, nls_gl.description);
    
  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     var id = rule_result.getGuidelineFilterConstant();
     
 //    OpenAjax.a11y.logger.debug(" Rule: " + rule_result.getRuleSummary() + " Rule id " + id  + "  Guideline id: " + guideline_id );     
  
     guideline.addRuleResult(id, rule_result, filter);
     
  }   
  
  return guideline;

};

/**
 * @method getFilteredRuleResultsByGuidelines
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules organized in a tree structure by WCAG 2.0 Guidelines
 *
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByGuidelines = function (filter) {

  var RULE_CATEGORIES  = OpenAjax.a11y.RULE_CATEGORIES;
  var WCAG20_GUIDELINE = OpenAjax.a11y.WCAG20_GUIDELINE;

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();
 
  var guidelines;
  var guideline;
  
  var evaluation_levels = this.evaluation_levels;

  guidelines = new OpenAjax.a11y.FilteredRuleResultsGroups(this, "wcag20_guidelines_summary", "WCAG 2.0 Guidelines");

  var nls_gl = nls_wcag20.getNLSItemById('1.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_1_1, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);

  nls_gl = nls_wcag20.getNLSItemById('1.2');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_1_2, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);  
  
  nls_gl = nls_wcag20.getNLSItemById('1.3');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_1_3, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('1.4');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_1_4, nls_gl.title, nls_gl.url_spec, nls_gl.description);  
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('2.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_2_1, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('2.2');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_2_2, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('2.3');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_2_3, nls_gl.title, nls_gl.url_spec, nls_gl.description);  
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('2.4');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_2_4, nls_gl.title, nls_gl.url_spec, nls_gl.description);  
  guidelines.addFilteredRuleResultsGroup(guideline);

  nls_gl = nls_wcag20.getNLSItemById('3.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_3_1, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('3.2');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_3_2, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);
  
  nls_gl = nls_wcag20.getNLSItemById('3.3');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_3_3, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);
    
  nls_gl = nls_wcag20.getNLSItemById('4.1');
  guideline = new OpenAjax.a11y.FilteredRuleResultsGroup(this, WCAG20_GUIDELINE.G_4_1, nls_gl.title, nls_gl.url_spec, nls_gl.description);
  guidelines.addFilteredRuleResultsGroup(guideline);

  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     var id = rule_result.getGuidelineFilterConstant();
  
     guidelines.addRuleResult(id, rule_result, filter);
     
  }   
  
  return guidelines;

};

/**
 * @method getFilteredRuleResultsBySuccessCriterion
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules associated with a WCAG 2.0 Success Criterion
 *
 * @param {Number}  success_criterion_id  -  Number representing the success criterion id 
 * @param {Number}  filter                -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsBySuccessCriterion = function (success_criterion_id, filter) {

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();

  var nls_sc = nls_wcag20.getNLSItemById(success_criterion_id);
  var success_criterion = new OpenAjax.a11y.FilteredRuleResultsGroup(this, success_criterion_id.toString(), nls_sc.title, nls_sc.url_spec, nls_sc.description);
    
  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];

     var id = rule_result.getSuccessCriterionFilterConstant().toString();
     if (typeof success_criterion_id === 'string') id = rule_result.getPrimarySuccessCriterion().id;
  
     success_criterion.addRuleResult(id, rule_result, filter);
     
     
     
  }   
  
  return success_criterion;

};


/**
 * @method getFilteredRuleResultsBySuccessCriteria
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules organized in a tree structure 
 *       by WCAG 2.0 Success Criteria
 *
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 * 
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by wcag 2.0 requirements
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsBySuccessCriteria = function (filter) {

  var RULE_CATEGORIES         = OpenAjax.a11y.RULE_CATEGORIES;
  var WCAG20_SUCCESS_CRITERIA = OpenAjax.a11y.WCAG20_SUCCESS_CRITERIA;

  var nls_wcag20 = OpenAjax.a11y.all_wcag20_nls.getNLS();
 
  var success_criteria;
  var sc;
  var nls_sc;
  
  var evaluation_levels = this.evaluation_levels;
  
//  OAA_WEB_ACCESSIBILITY_LOGGING.logger.debug("IN SUCCESS CRITERIA SUMMARY");

  success_criteria = new OpenAjax.a11y.FilteredRuleResultsGroups(this, "wcag20_success_criteria_summary", "WCAG 2.0 Success Criteria");

  if (nls_wcag20.getNLSItemById('1.1.1').level & evaluation_levels) {
    nls_sc = nls_wcag20.getNLSItemById('1.1.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.1.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
    
  if (nls_wcag20.getNLSItemById('1.2.1').level & evaluation_levels) {
    nls_sc = nls_wcag20.getNLSItemById('1.2.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.2').level & evaluation_levels) {
    nls_sc = nls_wcag20.getNLSItemById('1.2.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.6').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.6');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.6', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.7').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.7');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.7', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.8').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.8');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.8', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.2.9').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.2.9');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.2.9', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
    
  if (nls_wcag20.getNLSItemById('1.3.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.3.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.3.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.3.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.3.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.3.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.3.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.3.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.3.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.3').level & evaluation_levels) {    
    nls_sc = nls_wcag20.getNLSItemById('1.4.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.6').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.6');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.6', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.7').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.7');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.7', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.8').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.8');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.8', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('1.4.9').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('1.4.9');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '1.4.9', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.1.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.1.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.1.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.1.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.1.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.1.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.1.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.1.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.1.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.2.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.2.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.2.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.2.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.2.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);    
  }
  
  if (nls_wcag20.getNLSItemById('2.2.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.2.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.2.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.3.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.3.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.3.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.3.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.3.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.3.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
    
  if (nls_wcag20.getNLSItemById('2.4.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('2.4.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.6').level & evaluation_levels) {    
    nls_sc = nls_wcag20.getNLSItemById('2.4.6');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.6', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.7').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.7');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.7', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.8').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.8');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.8', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.9').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.9');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.9', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('2.4.10').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('2.4.10');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '2.4.10', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
    
  if (nls_wcag20.getNLSItemById('3.1.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.1.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.1.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.1.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.1.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.1.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.1.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);    
  }  
  
  if (nls_wcag20.getNLSItemById('3.1.6').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.1.6');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.1.6', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }  
  
  
  if (nls_wcag20.getNLSItemById('3.2.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.2.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.2.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.2.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.2.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.2.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.2.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.2.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
    
  if (nls_wcag20.getNLSItemById('3.3.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.3.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.3.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.3').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.3.3');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.3', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.4').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.3.4');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.4', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.5').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.3.5');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.5', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('3.3.6').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('3.3.6');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '3.3.6', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
    
  if (nls_wcag20.getNLSItemById('4.1.1').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('4.1.1');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '4.1.1', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }
  
  if (nls_wcag20.getNLSItemById('4.1.2').level & evaluation_levels) {  
    nls_sc = nls_wcag20.getNLSItemById('4.1.2');
    sc = new OpenAjax.a11y.FilteredRuleResultsGroup(this, '4.1.2', nls_sc.title, nls_sc.url_spec, nls_sc.description);
    success_criteria.addFilteredRuleResultsGroup(sc);  
  }

  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     var id = rule_result.getPrimarySuccessCriterion().id;
     
//     OpenAjax.a11y.logger.debug(" Rule Result ID: " + id);     
 
     success_criteria.addRuleResult(id, rule_result, filter);
     
  }   

  return success_criteria;
  
};


/**
 * @method getFilteredRuleResultsByRuleType
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules organized in a tree structure by rule type, defined in a ruleset
 *
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 *
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results that include tirage rules
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByRuleType = function (filter) {

  var groups = new OpenAjax.a11y.FilteredRuleResultsGroups(this, "", "Rules by Rule Type");
  
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, 'true', 'Required Rules');
  groups.addFilteredRuleResultsGroup(group);
  
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, 'false', 'Recommended Rules');
  groups.addFilteredRuleResultsGroup(group);
  
  
  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
     
     groups.addRuleResult(rule_result.isRuleRequired().toString(), rule_result, filter);
     
  }   

  return groups;

};


/**
 * @method getFilteredRuleResultsByRuleCategories
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of rules organized in a tree structure by rule category
 *
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 *
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results organized by rule categories
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByRuleCategories = function (filter) {

  var RULE_CATEGORIES = OpenAjax.a11y.RULE_CATEGORIES;

  var groups = new OpenAjax.a11y.FilteredRuleResultsGroups(this, RULE_CATEGORIES.ALL, "Rule Categories");
  var group;

  var nls_cache = OpenAjax.a11y.cache_nls;
  
  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.AUDIO_VIDEO);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.AUDIO_VIDEO, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.DATA_TABLES);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.DATA_TABLES, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.FORM_CONTROLS);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.FORM_CONTROLS, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.IMAGES);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.IMAGES, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.KEYBOARD_SUPPORT);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.KEYBOARD_SUPPORT, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.LINKS);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.LINKS, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.STRUCTURE_NAVIGATION);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.STRUCTURE_NAVIGATION, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.STYLE_READING_ORDER);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.STYLE_READING_ORDER, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  nls_item = nls_cache.getRuleCategory(RULE_CATEGORIES.WIDGETS_SCRIPTS);
  group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, RULE_CATEGORIES.WIDGETS_SCRIPTS, nls_item.title, nls_item.url, nls_item.desc);
  groups.addFilteredRuleResultsGroup(group);

  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
  
     groups.addRuleResult(rule_result.getRuleCategoryConstant(), rule_result, filter);
     
  }   
  
  return groups;

};

/**
 * @method getFilteredRuleResultsByRuleCategory
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of all rule results for a set of rule categories
 *
 * @param {Number}  category      -  Number of bit mask for which rule groups to include in the group 
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 *
 * @return {FilteredRuleResultsGroup}  The object containing the filtered rule results
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByRuleCategory = function (category, filter) {

  var nls_item = OpenAjax.a11y.cache_nls.getRuleCategory(category);
  var group = new OpenAjax.a11y.FilteredRuleResultsGroup(this, category, nls_item.title, nls_item.url, nls_item.desc);
    
  for (var i = 0; i < this.rule_results.length; i++) {
  
     var rule_result = this.rule_results[i];
     group.addRuleResult(rule_result.getRuleCategoryConstant(), rule_result, filter);
  }   
  
  return group;

};

/**
 * @method getFilteredRuleResultsByRuleSummary
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Returns an object containing a set of all rule results for a set of rule categories
 *
 * @param {Number}  option        -  Number representing the type of summary 
 * @param {Number}  filter        -  Filter for node results (bit mapped mask)
 *
 * @return {FilteredRuleResultsGroups}  The object containing the filtered rule results
 */

OpenAjax.a11y.EvaluationResult.prototype.getFilteredRuleResultsByRuleSummary = function (option, filter) {

  var RULE_SUMMARY = OpenAjax.a11y.RULE_SUMMARY;

  switch (option) {
  
  case RULE_SUMMARY.CATEGORIES:
    return this.getFilteredRuleResultsByRuleCategories(filter);
    break;
  
  case RULE_SUMMARY.WCAG20:
    return this.getFilteredRuleResultsByWCAG20(filter);
    break;

  case RULE_SUMMARY.PRINCIPLES:
    return this.getFilteredRuleResultsByPrinciples(filter);
    break;

  case RULE_SUMMARY.GUIDELINES:
    return this.getFilteredRuleResultsByGuidelines(filter);
    break;

  case RULE_SUMMARY.SUCCESS_CRITERIA:
    return this.getFilteredRuleResultsBySuccessCriteria(filter);
    break;

  case RULE_SUMMARY.RULESET_TYPE:
    return this.getFilteredRuleResultsByRuleType(filter);
    break;
  

  default:
    break;
  
  }
  
  return null;

};



/**
 * @method isSameDocument
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Determines if a window document property is the one for this evaluation 
 *
 * @param {Object}  document  -  Document Object Model (DOM) to be analyzed
 *
 * @param {Boolean}  True if the same document, false if not
 */
 
OpenAjax.a11y.EvaluationResult.prototype.isSameDocument = function (document) {
    
  return this.doc === document;
    
};
 

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.EvaluationResult
 *
 * @desc Creates a string representing the evaluation results in a JSON format
 *
 * @param  {Boolean}  node_results_option  -  Optional param, if false then node 
 *                                            results are not included in the JSON file
 *
 * @return {String} Returns a string in JSON format
 */
 
OpenAjax.a11y.EvaluationResult.prototype.toJSON = function (node_results_option) {

  if (typeof node_results_option !== 'boolean') node_results_option = false;
  
  var wcag20_nls  = OpenAjax.a11y.all_wcag20_nls.getNLS();  

  var json = "{\n";

  json += "  \"eval_url\"                  : \"" + OpenAjax.a11y.util.escapeForJSON(this.url)   + "\",\n";
  json += "  \"eval_title\"                : \"" + OpenAjax.a11y.util.escapeForJSON(this.title) + "\",\n";
  
  json += "  \"ruleset_id\"                : \"" + OpenAjax.a11y.util.escapeForJSON(this.ruleset_id)         + "\",\n";
  json += "  \"ruleset_title\"             : \"" + OpenAjax.a11y.util.escapeForJSON(this.ruleset_title)      + "\",\n";        
  json += "  \"ruleset_version\"           : \"" + OpenAjax.a11y.util.escapeForJSON(this.ruleset_version)    + "\",\n";          
  json += "  \"evaluation_levels_nls\"     : \"" + wcag20_nls.getEvaluationLevelsNLS(this.evaluation_levels) + "\",\n";
  json += "  \"evaluation_levels_code\"    : "   + this.evaluation_levels                                    + ",\n";
  json += "  \"recommended_rules_enabled\" : "   + this.recommended_rules_enabled                            + ",\n";

  json += "  \"element_counts\": {\n";

  json += "    \"all_element_count\"  : " + this.dom_cache.element_cache.dom_elements.length + ",\n";
  json += "    \"audio_count\"        : " + this.dom_cache.media_cache.audio_elements.length + ",\n";
  json += "    \"event_count\"        : " + this.dom_cache.controls_cache.elements_with_events.length + ",\n";
  json += "    \"form_control_count\" : " + this.dom_cache.controls_cache.control_elements.length + ",\n";
  json += "    \"frame_count\"        : " + this.dom_cache.frame_count + ",\n";
  json += "    \"heading_count\"      : " + this.dom_cache.headings_landmarks_cache.heading_elements.length + ",\n";
  json += "    \"iframe_count\"       : " + this.dom_cache.iframe_count + ",\n";
  json += "    \"image_count\"        : " + this.dom_cache.images_cache.image_elements.length + ",\n";
  json += "    \"landmark_count\"     : " + this.dom_cache.headings_landmarks_cache.landmark_elements.length + ",\n";
  json += "    \"link_count\"         : " + this.dom_cache.links_cache.link_elements.length + ",\n";
  json += "    \"list_count\"         : " + this.dom_cache.lists_cache.container_elements.length + ",\n";
  json += "    \"object_count\"       : " + this.dom_cache.media_cache.object_elements.length + ",\n";
  json += "    \"table_count\"        : " + this.dom_cache.tables_cache.table_elements.length + ",\n";
  json += "    \"video_count\"        : " + this.dom_cache.media_cache.video_elements.length + ",\n";
  json += "    \"widget_count\"       : " + this.dom_cache.controls_cache.widget_elements.length + "\n";

  json += "  },\n";

  var rule_results     = this.rule_results;
  var rule_results_len = rule_results.length;

  json += "  \"rule_results\": [\n";

  for (var i = 0; i < rule_results_len; i++) {
    
    var rule_result = rule_results[i];
    var rs = rule_result.getResultSummary();
    
    json += "    {  \"rule_id\"            : \"" + rule_result.getRuleId()                + "\",\n";
    json += "       \"rule_summary\"       : \"" + OpenAjax.a11y.util.escapeForJSON(rule_result.getRuleSummary())  + "\",\n";
    json += "       \"rule_category_nls\"  : \"" + rule_result.getRuleCategory()          + "\",\n";
    json += "       \"rule_category_code\" : "   + rule_result.getRuleCategoryConstant()  + ",\n";
    json += "       \"result_message\"     : \"" + OpenAjax.a11y.util.escapeForJSON(rule_result.getResultMessage()) + "\",\n";
    json += "       \"percent_passed\"     : "   + rs.percent_passed    + ",\n";
    json += "       \"passed\"             : "   + rs.passed            + ",\n";
    json += "       \"violations\"         : "   + rs.violations        + ",\n";
    json += "       \"warnings\"           : "   + rs.warnings          + ",\n";
    json += "       \"failures\"           : "   + rs.failures          + ",\n";
    json += "       \"manual_checks\"      : "   + rs.manual_checks     + ",\n";
    json += "       \"page\"               : "   + rs.page              + ",\n";
    json += "       \"hidden\"             : "   + rs.hidden            + "\n";

    if (node_results_option) {
      json += ",\n";

      json += "       \"node_results\"   : [\n";

      var node_results = rule_result.node_results_violations.concat(rule_result.node_results_warnings, rule_result.node_results_passed, rule_result.node_results_manual_checks, rule_result.node_results_page, rule_result.node_results_hidden);

      for (var j = 0; j < node_results.length; j++ ) {
     
        var node_result = node_results[j];
  
        json += "         {  \"result_value_nls\"  : \"" + node_result.getResultValue().label   + "\",\n";
        json += "            \"result_value_code\" : "   + node_result.getResultValueConstant() + ",\n";
        json += "            \"message\"           : \"" + node_result.getResultMessage()          + "\",\n";
        json += "            \"element\"           : \"" + node_result.cache_item.toString() + "\"\n";
        json += "         }";
       
        if (j < (node_results.length-1)) json += ",\n";
        else json += "\n";
      }
      
      json += "       ]\n";
      
    }  
    else {
      json += "\n";    
    }

    json += "    }";
    
    if (i < (rule_results_len-1))  json += ",\n";
    else json += "\n";

  }

  json += "                ]\n";

  json += "}\n";

  return json;
    
};
 

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                             RuleResult                           */
/* ---------------------------------------------------------------- */
 
 /** 
 * @constructor RuleResult
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructor for an object that contains a the results of 
 *          the evaluation of a ruleset rule
 *
 * @param  {RuleMapping}  rule_mapping  - RuleMapping object
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property  {String}   cache_id       - id used to identify the rule result object (uses the same value as the associated rule cache id)
 *
 * @property  {RuleMapping}  rule_mapping    - Reference to the assciated rule
 * @property  {Boolean}            rule_evaluated  - True if rule was evaluated, 
 *                                                   false if rule was disabled or 
 *                                                   not included becase of the WCAG 2.0 
 *                                                   level being evaluated
 *
 * @property  {String}  message          -  String message of rule implementation and correction 
 *
 * @property  {Array}  nodes_passed         - Array of all the node results 
 *                                            that passed
 * @property  {Array}  nodes_violations     - Array of all the node results 
 *                                            that resulted in violations
 * @property  {Array}  nodes_warnings       - Array of all the node results 
 *                                            that resulted in warnings
 * @property  {Array}  nodes_manual_checks  - Array of all the node results 
 *                                            that require manual evaluations
 * @property  {Array}  nodes_page           - Array of all the node results 
 *                                            that are associated with a page result
 * @property  {Array}  nodes_hidden         - Array of all the node results 
 *                                            that are hidden
 *
 * @property  {ResultSummary}  result_summary  - Summary of the node results for 
 *                                               the rule result
 */
 
OpenAjax.a11y.RuleResult = function (rule_mapping) {

  this.rule_mapping = rule_mapping;
  
  this.cache_id        = rule_mapping.rule.rule_id;
  
  this.message = '';

  this.node_results_passed         = [];
  this.node_results_violations     = [];
  this.node_results_warnings       = [];
  this.node_results_manual_checks  = [];
  this.node_results_page           = [];
  this.node_results_hidden         = [];
  
  this.result_summary = new OpenAjax.a11y.ResultSummary();
    
  this.rule_evaluated = false;

};

 /**
 * @method hasResults
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Tests if the rule applied to the content in the page 
 *
 * @return {Boolean} True if the rule has results, otherwise false
 */
 
OpenAjax.a11y.RuleResult.prototype.hasResults = function () {

   return this.result_summary.hasResults();

};


 /**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Gets numerical summary information about the rule results 
 *
 * @return {ResultSummary} Returns the ResultSummary object 
 */
OpenAjax.a11y.RuleResult.prototype.getResultSummary = function () {

  return this.result_summary;

};


 /**
 * @method getMessage
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Generates a localized rule result message
 *
 * @param {String}  id      -  Id of the rule result message string
 * @param {String}  prefix  -  Prefix message for the string
 *  
 * @return {String} Strings with rule result message
 */
OpenAjax.a11y.RuleResult.prototype.getMessage = function (id, prefix) {

  if (typeof prefix !== 'string') prefix = "";

  var nls_rules = OpenAjax.a11y.all_rules.rules_nls[OpenAjax.a11y.locale];
  
  var rule_id = this.getRuleId();

  var message = nls_rules.rules[rule_id]['RULE_RESULT_MESSAGES'][id];

  if (id === 'ACTION_NONE' && (typeof message !== 'string')) { 
    message = nls_rules.ACTION_NONE;
  }
  
  if (id === 'NOT_APPLICABLE' && (typeof message !== 'string')) { 
    message = nls_rules.NOT_APPLICABLE;
  }
  
  if (typeof message !== 'string' || (message.length === 0)) {
    message = "Message is missing for rule id: " + rule_id + " and mesage id: " + id;
  }  
  else {
    message = prefix + message;
  }
  
  var type = "";
  
  if (message.indexOf("%RULE_TYPE") >= 0) {
    
    if (this.rule_mapping.required) type = nls_rules.message_severities.MUST;
    else type = nls_rules.message_severities.SHOULD;

    message = message.replaceAll("%RULE_TYPE", type);  
  }
  
  var rs = this.result_summary;
  
  // Replace tokens with rule values

  message = message.replaceAll("%PER",  rs.percent_passed.toString());
  
  message = message.replaceAll("%N_F",  rs.failures.toString());
  
  message = message.replaceAll("%N_P",  rs.passed.toString());
  
  message = message.replaceAll("%N_T",  (rs.total + rs.manual_checks).toString());
  
  message = message.replaceAll("%N_MC", rs.manual_checks.toString());
  
  message = message.replaceAll("%N_PG", rs.page.toString());

  message = message.replaceAll("%N_H",  rs.hidden.toString());

  message = OpenAjax.a11y.util.transformElementMarkup(message);

  return message;
 
};


 /**
 * @method getResultMessages
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Generates a localized rule result messages 
 *
 * @param {SummaryResult} result_summary  - Optional summary result object to over
 *                                          ride the current result settings
 * 
 * @return {Array} An array of strings with rule result messages (typically only one string in the array)
 */
OpenAjax.a11y.RuleResult.prototype.getResultMessages = function (result_summary) {

  var save_flag = true;  // If data is from this object save
  
  // Test if data object defined and has required properties
  
  var rs = this.result_summary;
  if (typeof result_summary === 'object') { 
    rs = result_summary;
    save_flag = false;
  }
  else {
   // if message has already been created return
    if (this.messages && this.messages.length) return this.messages;
  }
  
  var messages = [];
  
  if ((rs.failures === 0) && (rs.manual_checks === 0)) {  
  
   if (rs.passed === 0) messages.push(this.getMessage('NOT_APPLICABLE'));
   else messages.push(this.getMessage('ACTION_NONE'));
    
  } 
  else {
  
    if (rs.failures > 0) {
      if (rs.failures === 1) messages.push(this.getMessage('ACTION_FAIL_S'));
      else messages.push(this.getMessage('ACTION_FAIL_P'));
    }  

    if (rs.manual_checks > 0) {
      if (rs.manual_checks === 1) messages.push(this.getMessage('ACTION_MC_S'));
      else messages.push(this.getMessage('ACTION_MC_P'));
    }
    
  }

  if (save_flag) this.messages = messages;

  return messages;
  
};

 /**
 * @method getResultMessage
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Generates a localized rule result messages 
 *
 * @param {SummaryResult} result_summary  - Optional summary result object to over
 *                                          ride the current result settings
 * 
 * @return {String} Returns a single string with all result messages
 */
OpenAjax.a11y.RuleResult.prototype.getResultMessage = function (result_summary) {

  var messages = this.getResultMessages(result_summary);
  var last = messages.length - 1;
  var m = "";
  
  for (var i = 0; i < messages.length; i++ ) {
    m += messages[i];
    if (i < last) m += "; ";
  }

  return m;
  
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of node results
 */

OpenAjax.a11y.RuleResult.prototype.getNodeResults = function () {
 
  function addResultNodes(items) {
    var i;
    var len = items.length;
    
    for (i = 0; i < len; i++) {
      result_nodes.push(items[i]);
    }    
  }

  var result_nodes = [];
  
  addResultNodes(this.node_results_passed);
  addResultNodes(this.node_results_violations);
  addResultNodes(this.node_results_warnings);
  addResultNodes(this.node_results_manual_checks);
  addResultNodes(this.node_results_page); 
  addResultNodes(this.node_results_hidden); 
  
  return result_nodes;
  
};



/**
 * @method addResult
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Adds a result of an evaluation of rule on a node in the dom  
 *
 * @param  {Number}  test_result         - Number representing if a node passed, failed, manual check or other test result
 * @param  {Object}  cache_item          - Reference to cache item associated with the test
 * @param  {String}  message_id          - Reference to the message string in the NLS file
 * @param  {Array}   message_arguements  - Array of values used in the message string 
 */

OpenAjax.a11y.RuleResult.prototype.addResult = function (test_result, cache_item, message_id, message_arguments, props) {

  var RESULT_VALUE    = OpenAjax.a11y.RESULT_VALUE;
  var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;

  if (!cache_item) return;
  
  var dom_element_item = null; 
 
  if (cache_item.dom_element) {
    dom_element_item = cache_item.dom_element;  
  } 
  else {
    dom_element_item = cache_item;  
  }
  
  dom_element_item.has_rule_results = true;
  
  var node_result_value = RESULT_VALUE.UNKNOWN;
  
  switch (test_result) {
  
  case TEST_RESULT.PASS:
    node_result_value = RESULT_VALUE.PASS;
    break;
    
  case TEST_RESULT.FAIL:
    if (this.rule_mapping.required) node_result_value = RESULT_VALUE.VIOLATION;
    else node_result_value = RESULT_VALUE.WARNING;
    break;
  
  case TEST_RESULT.MANUAL_CHECK:
    node_result_value = RESULT_VALUE.MANUAL_CHECK;
    break;
  
  case TEST_RESULT.PAGE:
    node_result_value = RESULT_VALUE.PAGE;
    break;
    
  case TEST_RESULT.HIDDEN:
    node_result_value = RESULT_VALUE.HIDDEN;
    break;
    
  default:
    break;  
  }   
  
  var node_result = new OpenAjax.a11y.NodeResult(this, node_result_value, cache_item, message_id, message_arguments, props);

//  OpenAjax.a11y.logger.debug("  ADD RESULT - text result: " + test_result + " cache item: " + cache_item + "  msg ID: " + message_id + " args: " + message_arguments);

  switch (node_result_value) {
 
  case RESULT_VALUE.HIDDEN: 
    this.node_results_hidden.push(node_result);
    if (dom_element_item)  dom_element_item.rules_hidden.push(node_result);
    this.result_summary.addHidden(1);
    break;

  case RESULT_VALUE.PAGE: 
    OpenAjax.a11y.logger.debug("  ADD RESULT - text result: " + test_result + " cache item: " + cache_item + "  msg ID: " + message_id + " args: " + message_arguments);    
    this.node_results_page.push(node_result);
    if (dom_element_item)  dom_element_item.rules_hidden.push(node_result);
    this.result_summary.addPage(1);
    break;

  case RESULT_VALUE.PASS:
    this.node_results_passed.push(node_result);
    if (dom_element_item) dom_element_item.rules_passed.push(node_result);
    this.result_summary.addPassed(1);
    break;
  
  case RESULT_VALUE.VIOLATION:
    this.node_results_violations.push(node_result);
    if (dom_element_item) dom_element_item.rules_violations.push(node_result);
    this.result_summary.addViolations(1);
    break;
  
  case RESULT_VALUE.WARNING:
    this.node_results_warnings.push(node_result);
    if (dom_element_item) dom_element_item.rules_warnings.push(node_result);
    this.result_summary.addWarnings(1);
    break;
  
  case RESULT_VALUE.MANUAL_CHECK:
    this.node_results_manual_checks.push(node_result);
    if (dom_element_item) dom_element_item.rules_manual_checks.push(node_result);
    this.result_summary.addManualChecks(1);
    break;

  default:
    break; 
  } // end switch 
    
};

/**
 * @method setEvaluationLevelToDisabled
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Sets evaluation level of the rule result to disabled 
 *       (i.e. rule was not evaluated due to user configuration settings)
 */

OpenAjax.a11y.RuleResult.prototype.setEvaluationLevelToDisabled = function () {

  this.is_disabled = true;
  

};  

/**
 * @method isRuleEnabled
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Tests whether the rule is enabled or disabled for evaluation  
 *
 * @param {Boolean}  True if rule is enabled, false if rule disabled
 */

OpenAjax.a11y.RuleResult.prototype.isRuleEnabled = function () {

  return this.rule_mapping.rule.isRuleEnabled();

};

/**
 * @method isRuleRequired
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Tests whether the rule is a required or recommended rule in this ruleset 
 *
 * @param {Boolean}  True if rule is a required rule, false if a recommended rule
 */

OpenAjax.a11y.RuleResult.prototype.isRuleRequired = function () {

  return this.rule_mapping.required;
  
};

/**
 * @method getRuleRequiredYesNo
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns 'Yes' or "No' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Yes" if required, otherwise "No" 
 */

OpenAjax.a11y.RuleResult.prototype.getRuleRequiredYesNo = function () {

  return OpenAjax.a11y.cache_nls.getYesNoNLS(this.isRuleRequired());
  
};

/**
 * @method getRuleRequiredOrRecommended
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns 'Required' or "Recommended' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Required" if required, otherwise "Recommended" 
 */

OpenAjax.a11y.RuleResult.prototype.getRuleRequiredOrRecommended = function () {

  var cache_nls = OpenAjax.a11y.cache_nls.getCacheNLS();
  
  if (this.isRuleRequired()) return cache_nls.required;
  
  return cache_nls.recommended;
  
};



/**
 * @method getRuleId
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get the programmatic id that uniquely identifies the rule
 *
 * @return {String} The rule id
 */

OpenAjax.a11y.RuleResult.prototype.getRuleId = function () {

  return this.rule_mapping.rule.getRuleId();
   
};

/**
 * @method getRuleIdNLS
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a localized human readable id for uniquely identifying the rule
 *
 * @return {String} Localized string of the rule id
 */

OpenAjax.a11y.RuleResult.prototype.getRuleIdNLS = function () {

  return this.rule_mapping.rule.getRuleIdNLS();
   
};

/**
 * @method getRuleCategoryConstant
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns the numeric value for the rule category
 * 
 * @return {Number}  Numeric value of the rule category
 */

OpenAjax.a11y.RuleResult.prototype.getRuleCategoryConstant = function () {

  return this.rule_mapping.rule.getRuleCategoryConstant();
  
};

/**
 * @method getRuleCategory
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a localized title, url and description of the rule category
 * 
 * @return {Object}  Returns an object with the following propertues:<br/>
 *                   'title':  String representing the Title of the rule category <br/>
 *                   'desc':   String providing a longer description of the rule category<br/>
 *                   'url':    URL to more information about the rule category (maybe blank)<br/>
 */

OpenAjax.a11y.RuleResult.prototype.getRuleCategory = function () {

  return this.rule_mapping.rule.getRuleCategory();
  
};

/**
 * @method getRuleScope
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a localized string of the rule scope (i.e. 'element' or 'page')
 *
 * @return {String} Localized string of the rule scope
 */
 
OpenAjax.a11y.RuleResult.prototype.getRuleScope = function () {

  return this.rule_mapping.rule.getRuleScope();
   
};

/**
 * @method isScopePage
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of page, otherwise false
 */
 
OpenAjax.a11y.RuleResult.prototype.isScopePage = function () {

  return this.rule_mapping.rule.isScopePage();
  
};



/**
 * @method isScopeElement
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of element, otherwise false
 */
 
OpenAjax.a11y.RuleResult.prototype.isScopeElement = function () {

  return this.rule_mapping.rule.isScopeElement();
  
};





/**
 * @method getRuleDefinition
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Gets the definition of the rule
 *
 * @return {String} Localized string of the rule definition based on being 
 *                  required or recommended
 */
OpenAjax.a11y.RuleResult.prototype.getRuleDefinition = function () {

  return this.rule_mapping.rule.getRuleDefinition(this.rule_mapping.required);
  
};

/**
 * @method getRuleSummary
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Gets the summary of the rule
 *
 * @return {String} Localized string of the rule summary based on being 
 *                  required or recommended
 */

OpenAjax.a11y.RuleResult.prototype.getRuleSummary = function () {

  return this.rule_mapping.rule.getRuleSummary(this.rule_mapping.required);
  
};

/**
 * @method getPurpose
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Gets an array strings representing the purpose, basically
 *       how does the rule help people with disabilities
 *
 * @return  {Array}  Returns an array of localized string describing the purpose
 */
 
OpenAjax.a11y.RuleResult.prototype.getPurpose = function () {

  return this.rule_mapping.rule.getPurpose();
  
};

/**
 * @method getTargetResourcesDescription
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a description of the markup or page feature the rule is evaluates
 *
 * @return  {String}  Localized string representing the markup or page feature 
 *                    tested by the rule
 */
OpenAjax.a11y.RuleResult.prototype.getTargetResourcesDescription = function () {

  return this.rule_mapping.rule.getTargetResourcesDescription();

};

/**
 * @method getTargetResources
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Returns an localized array strings representing target resources of 
 *       the rule
 *
 * @return  {Array}  Returns an array of strings identifying the elements and/or
 *                    attributes that the rule evaluates
 */
OpenAjax.a11y.RuleResult.prototype.getTargetResources = function () {

  return this.rule_mapping.rule.getTargetResources();

};

/**
 * @method getTargetResourceProperties
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get the attributes and properties of element used in evaluating a rule
 *
 * @return  {Array}  Returns an array of strings identifying the elements and/or
 *                    attributes that the rule evaluates
 */
 
OpenAjax.a11y.RuleResult.prototype.getTargetResourceProperties = function () {
  
  return this.rule_mapping.rule.getTargetResourceProperties();
  
};

/**
 * @method getTechniques
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get the techniques to implement the requirements of the rule 
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title' : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
 
OpenAjax.a11y.RuleResult.prototype.getTechniques = function () {

  return this.rule_mapping.rule.getTechniques();
  
};

/**
 * @method getManualCheckProcedures
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Gets manual checking proceedures for evaluating the rule
 *       requirements
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title' : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
OpenAjax.a11y.RuleResult.prototype.getManualCheckProcedures = function () {

  return this.rule_mapping.rule.getManualCheckProcedures();

};

/**
 * @method getInformationalLinks
 *
 * @memberOf OpenAjax.a11y.cache.RuleResult
 *
 * @desc Get information links related to understanding or implementation of the rule
 *
 * @return  {Array}  Returns an array of objects, each object includes the following properties:<br/>
 *                   'type_const' : Number representing the type of information,<br/> 
 *                   'title'      : Title descriping the type of information, <br/>
 *                   'url'        : Link to more information <br/>
 *
 * @example
 *
 * var node_list = [];
 * var info_links = rule.getInformationalLinks();
 * 
 * for(var i = 0; i < info_links.length; i++) {
 *   var info_link = info_links[i];
 *
 *   // Using object properties to create a link element
 *   var node = document.createElement('a');
 *   node.appendChild(document.createTextNode(info_link.title));
 *   node.setAttribute('href',  info_link.url);
 *   node.setAttribute('class', info_link.type_const.toString());
 *
 *   node_list.push(node);
 * }
 */

OpenAjax.a11y.RuleResult.prototype.getInformationalLinks = function () {

  return this.rule_mapping.rule.getInformationalLinks();

};

/**
 * @method getPrimarySuccessCriterion
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get information about primary WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Object}  Object representing the success criteria, 
 *                    each object has the following properties:<br/> 
 *                    'id'           : A "P.G.SC" formatted string representing the SC,<br/>  
 *                    'title'        : A localized title for the SC,<br/>
 *                    'description'  : A localized description of the SC,<br/>
 *                    'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                    'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 *
 * @example
 *
 * var sc_info = rule.getPrimarySuccessCriterion();
 * 
 * // Creating a link element to the primary success criterion
 * var node = document.createElement('a');
 * node.appendChild(document.createTextNode(sc_info.id + " " + sc_info.title));
 * node.setAttribute('href',  sc_info.url);
 * node.setAttribute('title', sc_info.description);
 * } 
 */

OpenAjax.a11y.RuleResult.prototype.getPrimarySuccessCriterion = function () {

  return this.rule_mapping.rule.getPrimarySuccessCriterion();

};

/**
 * @method getRelatedSuccessCriteria
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get information about the related WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Array}  Array of objects representing the success criteria, each object has the 
 *                   following properties: <br/>
 *                   'id'           : A "P.G.SC" formatted string representing the SC, <br/> 
 *                   'title'        : A localized title for the SC,<br/>
 *                   'description'  : A localized description of the SC,<br/>
 *                   'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                   'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 */

OpenAjax.a11y.RuleResult.prototype.getRelatedSuccessCriteria = function () {

  return this.rule_mapping.rule.getRelatedSuccessCriteria();

};

/**
 * @method getWCAG20LevelConstant
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get the numerical constant for the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {Number}  Number representing the WCAG 2.0 level 
 */

OpenAjax.a11y.RuleResult.prototype.getWCAG20LevelConstant = function () {

  return this.rule_mapping.rule.getWCAG20LevelConstant();

};

/**
 * @method getWCAG20Level
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get the string representation of the the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {String}  String representing the WCAG 2.0 success criterion level 
 *                    (i.e. A, AA or AAA)
 */

OpenAjax.a11y.RuleResult.prototype.getWCAG20Level = function () {

  return this.rule_mapping.rule.getWCAG20Level();

};


/**
 * @method getPrincipleFilterConstant
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a bit maskable filter constant that represents the WCAG 2.0 Principle
 *
 * @return  {Number}  Number representing the WCAG 2.0 Principle
 */

OpenAjax.a11y.RuleResult.prototype.getPrincipleFilterConstant = function () {

   return this.rule_mapping.rule.getPrincipleFilterConstant();
   
};

/**
 * @method getGuidelineFilterConstant
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a bit maskable filter constant that represents the WCAG 2.0 Guideline 
 *
 * @return  {Number}  Number representing the WCAG 2.0 Guideline
 */

OpenAjax.a11y.RuleResult.prototype.getGuidelineFilterConstant = function () {

   return this.rule_mapping.rule.getGuidelineFilterConstant();
   
};

/**
 * @method getSuccessCriterionFilterConstant
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Get a filter constant that represents the WCAG 2.0 Success Criterion 
 *
 * @return  {Number}  Number representing the WCAG 2.0 Success Criterion
 */

OpenAjax.a11y.RuleResult.prototype.getSuccessCriterionFilterConstant = function () {

   return this.rule_mapping.rule.getSuccessCriterionFilterConstant();
   
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.RuleResult
 *
 * @desc Creates a text string representation of the rule result object 
 *
 * @return {String} Returns a text string representation of the rule result object
 */

OpenAjax.a11y.RuleResult.prototype.toString = function () {

 var str = this.getRuleDefinition() + " (";
 
 if (this.result_summary.hasResults()) {
   if (this.result_summary.passed || this.result_summary.failures) str += this.result_summary.percent_passed + " percent passed";
   if (this.result_summary.manual_checks) str += " " + this.result_summary.manual_checks + " manual checks";
 }
 else {
   str += "no results";
 }  

 str += ")"; 

 return str;
 
};

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                             NodeResult                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor NodeResult
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructor for an object that contains a the results of 
 *          the evaluation of a rule on a node
 *
 * @param  {ResultRule} rule_result             - reference to the rule result object
 * @param  {Number}     result_value            - Constant representing result value of the evaluation result
 * @param  {DOMElement} cache_item              - Object reference to cache item associated with the test
 * @param  {String}     message_id              - String reference to the message string in the NLS file
 * @param  {Array}      message_arguements      - Array  array of values used in the message string 
 * @param  {Array}      props                   - Array of properties that are defined in the validation function (NOTE: typically undefined)
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property  {RuleResult} rule_result         - reference to the rule result object
 * @property  {Number}     result_value        - Constant representing result value of the evaluation result
 * @property  {DOMElement} cache_item          - Object reference to cache item associated with the test
 * @property  {String}     message_id          - String reference to the message string in the NLS file
 * @property  {Array}      message_arguments   - Array  array of values used in the message string  
 * @property  {Array}      props               - Array of properties that are defined in the validation function (NOTE: typically undefined)
 */

OpenAjax.a11y.NodeResult = function (rule_result, result_value, cache_item, message_id, message_arguments, props) {

  this.rule_result = rule_result;
  
  this.result_value      = result_value;
  this.cache_item        = cache_item;
  this.message_id        = message_id;
  this.message_arguments = message_arguments;
  this.cache_id          = rule_result.cache_id;
  
//  OpenAjax.a11y.logger.debug("Rule: " + this.getRuleId() + "Prop: " + typeof props);
  
  this.props = [];
  if (typeof props === 'object') this.props = props;
  
};

 /**
 * @method getResultMessage
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns an localized node result message 
 *
 * @return {String} String with node result message
 */
OpenAjax.a11y.NodeResult.prototype.getResultMessage = function () {

  var nls_rules = OpenAjax.a11y.all_rules.rules_nls[OpenAjax.a11y.locale];
  
  var RESULT_VALUE = OpenAjax.a11y.RESULT_VALUE;
  
  var message;
  
  // If no message id return the empty string
  if (this.message_id.length === 0) return "";
  
  var rule_id = this.getRuleId();
  
//  OpenAjax.a11y.logger.debug("Rule: " + rule_id);
  
  var str = nls_rules.rules[rule_id]['NODE_RESULT_MESSAGES'][this.message_id];
  
  if (!str) return nls_rules.missing_message + this.message_id;
    
//    OpenAjax.a11y.logger.debug("Rule: " + rule_id + " Message: " + str);

  var vstr; // i.e. %1, %2 ....
  var message_arguments_len = this.message_arguments.length;

  // check to see if message has result value dependence
  
  vstr = "%s";
  
  if (str.indexOf(vstr) >= 0) {
    
    switch (this.result_value) {
    case RESULT_VALUE.VIOLATION:
      message = nls_rules.message_severities.MUST;
      break;

    case RESULT_VALUE.WARNING:
      message = nls_rules.message_severities.SHOULD;
      break;

    case RESULT_VALUE.MANUAL_CHECK:
      message = nls_rules.message_severities.MAY;
      break;

    default:
      message = "";
      break; 
    }

    str = str.replace(vstr, message);  
  }
  
  // Replace 
  
  for (var i = 0; i < message_arguments_len; i++) { 
    vstr = "%" + (i+1); 
    message = this.message_arguments[i];
    
    if (typeof message === 'string') {
      message = message.normalizeSpace();
    }
    else {
      if (typeof message === 'number') {
        message = message.toString();
      }
      else {
        message = "";
      }  
    }  
    str = str.replace(vstr, message);
  } // end loop

  return OpenAjax.a11y.util.transformElementMarkup(str);
  
};

/**
 * @method getResultProperties
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get the DOM cache values for the properties associated with a rule
 * 
 * @return {Array} Array of objects with the following properties:<br/>
 *                 'label'       : String label of the property<br/>
 *                 'value'       : String value of the property<br/>
 *                 'description' : String providing additional information about the property <br/>
 */

OpenAjax.a11y.NodeResult.prototype.getResultProperties = function () {

  var cache_nls = OpenAjax.a11y.cache_nls;

  var nls_prop_list = [];
  
  var prop_list = this.rule_result.getTargetResourceProperties();
  var value;
  var prop_item;
  var nls_item;
  var i;

  for (i = 0; i < prop_list.length; i++) {

    prop_item = prop_list[i];

    value    = this.cache_item.getCachePropertyValue(prop_item);

    nls_item = cache_nls.getLabelAndValueNLS(prop_item, value);

    nls_prop_list.push(nls_item);
    
  }  
  
  for (i = 0; i < this.props.length; i++) {

    nls_prop_list.push(this.props[i]);
    
  }  
  
  
  return nls_prop_list;
   
};



/**
 * @method getResultValueConstant
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Gets the numerical value of the result value of the result 
 *
 * @return {Number} Returns a number representing the result value of the result
 */

OpenAjax.a11y.NodeResult.prototype.getResultValueConstant = function () {

  return this.result_value;
 
};

/**
 * @method getResultValue
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Gets result value label, abbreviation, description and style 
 *
 * @return {Object} Returns a object with the following properties: <br/>
 *                  'label'       : String representing the result value <br/>
 *                  'abbrev'      : Abbreviation string of the label<br/>
 *                  'description' : String describing the result value<br/>
 *                  'style'       : String that can used for styling the label<br/> 
 */

OpenAjax.a11y.NodeResult.prototype.getResultValue = function () {

  return  OpenAjax.a11y.cache_nls.getResultValueNLS(this.result_value);
 
};


/**
 * @method getOrdinalPosition
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns a the ordinal position of the element in a list of node results
 * 
 * @return {Number} Returns a number indicating the position in a list of node results
 */

OpenAjax.a11y.NodeResult.prototype.getOrdinalPosition = function () {

  var position = 0;

  if (this.cache_item) {
    if(this.cache_item.dom_element) position = this.cache_item.dom_element.document_order;
    else position = this.cache_item.document_order;
  }
  
  return position;
   
};


/**
 * @method isRuleEnabled
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Tests whether the rule is enabled or disabled for evaluation  
 *
 * @param {Boolean}  True if rule is enabled, false if rule disabled
 */

OpenAjax.a11y.NodeResult.prototype.isRuleEnabled = function () {

  return this.rule_result.isRuleEnabled();

};

/**
 * @method isRuleRequired
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Tests whether the rule is mapped as a required or recommended rule  
 *
 * @param {Boolean}  True if rule is a required rule, false if a recommended rule
 */

OpenAjax.a11y.NodeResult.prototype.isRuleRequired = function () {

  return this.rule_result.isRuleRequired();

};


/**
 * @method getRuleRequiredYesNo
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns 'Yes' or "No' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Yes" if required, otherwise "No" 
 */

OpenAjax.a11y.NodeResult.prototype.getRuleRequiredYesNo = function () {

  return this.rule_result.getRuleRequiredYesNo();
  
};

/**
 * @method getRuleRequiredOrRecommended
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns 'Required' or "Recommended' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Required" if required, otherwise "Recommended" 
 */

OpenAjax.a11y.NodeResult.prototype.getRuleRequiredOrRecommended = function () {

  return this.rule_result.getRuleRequiredOrRecommended();
  
};


/**
 * @method getRuleId
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get the programmatic id that uniquely identifies the rule
 *
 * @return {String} The rule id
 */

OpenAjax.a11y.NodeResult.prototype.getRuleId = function () {

  return this.rule_result.getRuleId();
   
};

/**
 * @method getRuleIdNLS
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get a localized human readable id for uniquely identifying the rule
 *
 * @return {String} Localized string of the rule id
 */

OpenAjax.a11y.NodeResult.prototype.getRuleIdNLS = function () {

  return this.rule_result.getRuleIdNLS();
   
};


/**
 * @method getRuleCategory
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get a localized title, url and description of the rule category
 * 
 * @return {Object}  Returns an object with the following propertues:<br/>
 *                   'title':  String representing the Title of the rule category <br/>
 *                   'desc':   String providing a longer description of the rule category<br/>
 *                   'url':    URL to more information about the rule category (maybe blank)<br/>
 */

OpenAjax.a11y.NodeResult.prototype.getRuleCategory = function () {

  return this.rule_result.getRuleCategory();
  
};

/**
 * @method getRuleCategoryConstant
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns the numeric value for the rule category
 * 
 * @return {Number}  Numeric value of the rule category
 */

OpenAjax.a11y.NodeResult.prototype.getRuleCategoryConstant = function () {

  return this.rule_result.getRuleCategoryConstant();
  
};


/**
 * @method getRuleScope
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get a localized string of the rule scope (i.e. 'element' or 'page')
 *
 * @return {String} Localized string of the rule scope
 */
 
OpenAjax.a11y.NodeResult.prototype.getRuleScope = function () {

  return this.rule_result.getRuleScope(); 
  
};

/**
 * @method isScopePage
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of page, otherwise false
 */
 
OpenAjax.a11y.NodeResult.prototype.isScopePage = function () {

  return this.rule_result.isScopePage(); 
  
};

/**
 * @method isScopeElement
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of element, otherwise false
 */
 
OpenAjax.a11y.NodeResult.prototype.isScopeElement = function () {

  return this.rule_result.isScopeElement(); 
  
};

/**
 * @method getRuleDefinition
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Gets the definition of the rule
 *
 * @return {String} Localized string of the rule definition based on being 
 *                  required or recommended
 */

OpenAjax.a11y.NodeResult.prototype.getRuleDefinition = function () {

  return this.rule_result.getRuleDefinition();
  
};

/**
 * @method getRuleSummary
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc  Gets the summary of the rule
 *
 * @return {String} Localized string of the rule summary based on being 
 *                  required or recommended
 */

OpenAjax.a11y.NodeResult.prototype.getRuleSummary = function () {

  return this.rule_result.getRuleSummary();
  
};

/**
 * @method getPurpose
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Gets an array strings representing the purpose, basically
 *       how does the rule help people with disabilities
 *
 * @return  {Array}  Returns an array of localized string describing the purpose
 */
OpenAjax.a11y.NodeResult.prototype.getPurpose = function () {

  return this.rule_result.getPurpose();

};


/**
 * @method getTargetResourcesDescription
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get a description of the markup or page feature the rule is evaluates
 *
 * @return  {String}  Localized string representing the markup or page feature 
 *                    tested by the rule
 */
OpenAjax.a11y.NodeResult.prototype.getTargetResourcesDescription = function () {

  return this.rule_result.getTargetResourcesDescription();

};

/**
 * @method getTargetResources
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns an localized array strings representing target resources of 
 *       the rule
 *
 * @return  {Array}  Returns an array of strings identifying the elements and/or
 *                    attributes that the rule evaluates
 */
OpenAjax.a11y.NodeResult.prototype.getTargetResources = function () {

  return this.rule_result.getTargetResources();

};


/**
 * @method getTechniques
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get the techniques to implement the requirements of the rule 
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title'    : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
 
OpenAjax.a11y.NodeResult.prototype.getTechniques = function () {

  return this.rule_result.getTechniques();
  
};

/**
 * @method getManualCheckProcedures
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Gets manual checking proceedures for evaluating the rule
 *       requirements
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title' : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
 
OpenAjax.a11y.NodeResult.prototype.getManualCheckProcedures = function () {

  return this.rule_result.getManualCheckProcedures();

};

/**
 * @method getInformationalLinks
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get information links related to understanding or implementation of the rule
 *
 * @return  {Array}  Returns an array of objects, each object includes the following properties:<br/>
 *                   'type_const' : Number representing the type of information,<br/> 
 *                   'title'      : Title descriping the type of information, <br/>
 *                   'url'        : Link to more information <br/>
 *
 * @example
 *
 * var node_list = [];
 * var info_links = rule.getInformationalLinks();
 * 
 * for(var i = 0; i < info_links.length; i++) {
 *   var info_link = info_links[i];
 *
 *   // Using object properties to create a link element
 *   var node = document.createElement('a');
 *   node.appendChild(document.createTextNode(info_link.title));
 *   node.setAttribute('href',  info_link.url);
 *   node.setAttribute('class', info_link.type_const.toString());
 *
 *   node_list.push(node);
 * }
 */
OpenAjax.a11y.NodeResult.prototype.getInformationalLinks = function () {

  return this.rule_result.getInformationalLinks();

};


/**
 * @method getPrimarySuccessCriterion
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get information about primary WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Object}  Object representing the success criteria, 
 *                    each object has the following properties:<br/> 
 *                    'id'           : A "P.G.SC" formatted string representing the SC,<br/>  
 *                    'title'        : A localized title for the SC,<br/>
 *                    'description'  : A localized description of the SC,<br/>
 *                    'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                    'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 *
 * @example
 *
 * var sc_info = rule.getPrimarySuccessCriterion();
 * 
 * // Creating a link element to the primary success criterion
 * var node = document.createElement('a');
 * node.appendChild(document.createTextNode(sc_info.id + " " + sc_info.title));
 * node.setAttribute('href',  sc_info.url);
 * node.setAttribute('title', sc_info.description);
 * } 
 */

OpenAjax.a11y.NodeResult.prototype.getPrimarySuccessCriterion = function () {

  return this.rule_result.getPrimarySuccessCriterion();

};

/**
 * @method getRelatedSuccessCriteria
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get information about the related WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Array}  Array of objects representing the success criteria, each object has the 
 *                   following properties: <br/>
 *                   'id'           : A "P.G.SC" formatted string representing the SC, <br/> 
 *                   'title'        : A localized title for the SC,<br/>
 *                   'description'  : A localized description of the SC,<br/>
 *                   'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                   'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 */

OpenAjax.a11y.NodeResult.prototype.getRelatedSuccessCriteria = function () {

  return this.rule_result.getRelatedSuccessCriteria();

};

/**
 * @method getWCAG20LevelConstant
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get the numerical constant for the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {Number}  Number representing the WCAG 2.0 level 
 */

OpenAjax.a11y.NodeResult.prototype.getWCAG20LevelConstant = function () {

  return this.rule_result.getWCAG20LevelConstant();

};

/**
 * @method getWCAG20Level
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Get the string representation of the the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {String}  String representing the WCAG 2.0 success criterion level 
 *                    (i.e. A, AA or AAA)
 */

OpenAjax.a11y.NodeResult.prototype.getWCAG20Level = function () {

  return this.rule_result.getWCAG20Level();

};

/**
 * @method getXPath
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns the xpath of the associated element
 * 
 * @return {String} information about the node result 
 */

OpenAjax.a11y.NodeResult.prototype.getXPath = function () {
  
  return this.getDOMElement().xpath;
   
};


/**
 * @method getDOMElement
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns the dom element object
 *
 * @return {String} Returns a dom element associated with the cache item
 */

OpenAjax.a11y.NodeResult.prototype.getDOMElement = function () {

  if (this.cache_item.dom_element) 
    return this.cache_item.dom_element;
  else
    return this.cache_item;      
  
};

/**
 * @method getCacheItem
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Gets the cache item associated with the node result
 *
 * @return {Object} Returns a cache item object
 */

OpenAjax.a11y.NodeResult.prototype.getCacheItem = function () {

  return this.cache_item;
 
};


/**
 * @method getPropertyValue
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Returns the value of a property in the cache 
 *
 * @param  {String}  property  -  Property of the cache element object 
 *
 * @return {value | null} Returns a value if property is defined, null if not
 */

OpenAjax.a11y.NodeResult.prototype.getPropertyValue = function (property) {

  var value;

  value = this.cache_item[property];  
  if (typeof value == 'string' || typeof value == 'boolean' || typeof value == 'number') return value;
  
  value = this.cache_item.dom_element[property]; 
  if (value || typeof value == 'boolean' || typeof value == 'number') return value;  

  value = this.cache_item.dom_element.computed_style[property]; 
  if (value || typeof value == 'boolean' || typeof value == 'number') return value;  

  value = this.cache_item.dom_element.events[property]; 
  if (value || typeof value == 'boolean' || typeof value == 'number') return value;  
   
   
  return null;
  
};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Creates a text string representation of the node result object 
 *
 * @return {String} Returns a text string representation of the node result object
 */

OpenAjax.a11y.NodeResult.prototype.toString = function () {

  return this.getResultMessage();
  
};


/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.NodeResult
 *
 * @desc Creates JSON object descibing the properties of the node result
 *
 * @param {String} prefix  -  A prefix string typically spaces
 * 
 * @return String information about the node result 
 */

OpenAjax.a11y.NodeResult.prototype.toJSON = function (prefix) {

  var next_prefix = "";
  
  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else next_prefix = prefix + "    ";

  var result_value_nls = this.getResultValue();
  
  var json = "";

  json += prefix + "{ \"result_label\"    : \"" + result_value_nls.label      + "\",";
  json += prefix + "  \"result_style\"    : \"" + result_value_nls.style      + "\",";
  json += prefix + "  \"result_abbrev\"   : \"" + result_value_nls.abbrev     + "\",";
  json += prefix + "  \"rule_id\"         : \"" + this.getRuleId()            + "\",";
  json += prefix + "  \"nls_rule_id\"     : \"" + this.getRuleIdNLS()         + "\",";
  json += prefix + "  \"required\"        : "   + this.isRuleRequired()       + ",";
  json += prefix + "  \"required_nls\"    : \"" + this.getRuleRequiredYesNo() + "\",";
  json += prefix + "  \"wcag_primary_id\" : \"" + this.getPrimarySuccessCriterion().id  + "\",";
  json += prefix + "  \"wcag_level\"      : \"" + this.getWCAG20Level()     + "\",";
  json += prefix + "  \"message\"         : \"" + OpenAjax.a11y.util.escapeForJSON(this.getResultMessage()) + "\",";

  var result_props     = this.getResultProperties();
  var result_props_len = result_props.length;
  var last = result_props_len - 1;
  
  if (result_props_len > 0) {
    json += prefix + "  \"properties\" : [";
    for (var i = 0; i < result_props_len; i++) {
      var result_prop = result_props[i];
      if (i === last) json += next_prefix + "{ \"label\" : \"" + result_prop.label + "\", \"value\" : \"" + OpenAjax.a11y.util.escapeForJSON(result_prop.value) + "\"}";
      else json += next_prefix + "{ \"label\" : \"" + result_prop.label + "\", \"value\" : \"" + OpenAjax.a11y.util.escapeForJSON(result_prop.value) + "\"},";
    }
    json += prefix + "  ]";
  }
  else {
    json += prefix + "  \"properties\" : []";
  }
    
  json += prefix + "}";
  
  return json;
};

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                      FilteredCacheItemResults                    */
/* ---------------------------------------------------------------- */

/**
 * @constructor FilteredCacheItemResults
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructs a data structure of cache items associated with a rule category 
 *       The cache items returned can be filtered by the tpe of evaluation result 
 *
 * @param  {Object}  evaluation_result  - Evaluation result object containing the evaluation results
 *
 * @param  {String}  title     - Title for the group 
 * @param  {String}  url       - URL to more information on the group  
 * @param  {String}  desc      - Description of the group 
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property  {Boolean} is_tree       - At least one of the CacheItemResults contains child items
 * @propert   {Object}  ruleset       - Ruleset object containing the evaluation results
 * @property  {Object}  dom_cache     - dom cache to use in generating filtered results 
 *
 * @property  {Number}  element_type  - Element type(s) included in this cache list;
 * @property  {Number}  filter        - Node result filter used on the list
 * 
 * @property  {Array}   cache_item_results        - list of top level cache item results
 * 
 * @property  {Boolean}  has_elements  - True if group contains at least one element 
 *
 * @property  {Boolean}  summary_result     - Summary of the node results for 
 *                                            the rule  
 */

 OpenAjax.a11y.FilteredCacheItemResults = function(eval_results, title, url, desc) {

  this.title = "";
  this.url = "";
  this.desc = "";
  
  if (typeof title === 'string') this.title = title;
  if (typeof url   === 'string') this.url = url;
  if (typeof desc  === 'string') this.desc = desc;

  this.is_tree = false;
  this.evaluation_results = eval_results;
  this.dom_cache = eval_results.dom_cache;
  
  this.element_type = OpenAjax.a11y.ELEMENT_TYPE.UNDEFINED;
  this.filter       = OpenAjax.a11y.RESULT_FILTER.ALL;
  
  this.has_elements = false;
  
  this.result_summary = new OpenAjax.a11y.ResultSummary();
      
  this.cache_item_results = [];
  
};

/**
 * @method getTitle
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Returns the title of the group
 *
 * @return  {String} String representing the title of the group
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.getTitle = function() {

  return this.title;
};

/**
 * @method getURL
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Returns the url of the group, can be empty
 *
 * @return  {String} String of the url to more information about a group
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.getURL = function() {

  return this.url;
};

/**
 * @method getDescription
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Returns the description of the group, can be empty
 *
 * @return  {String} String describing the group of rules
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.getDescription = function() {

  return this.desc;
};

/**
 * @method isTree
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Tests if the cache items has a tree structure (otherwise simple list)
 *
 * @return  {Boolean}  true if cache items are organized as a tree, otherwise false
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.isTree = function() {

  return this.is_tree;
};


 /**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Gets numerical summary information about the cache item results 
 *
 * @return {ResultSummary} Returns the ResultSummary object 
 */
 
OpenAjax.a11y.FilteredCacheItemResults.prototype.getResultSummary = function () {

  return this.result_summary;

};



/**
 * @method hasResults
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Tests if any of the rules in this group applied to the content in the page 
 *
 * @return {Boolean} True if any of the rule have results, otherwise false
 */
 
OpenAjax.a11y.FilteredCacheItemResults.prototype.hasResults = function () {

   return this.result_summary.hasResults();

};

/**
 * @method hasElements
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Tests if any of their are any elements of this type  
 *
 * @return {Boolean} True if the group contains at least one element, otherwise false
 */
 
OpenAjax.a11y.FilteredCacheItemResults.prototype.hasElements = function () {

   return this.has_elements;

};

/**
 * @method updateSummary
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 * 
 * @desc Updated summary result counts with cache item results
 *
 * @param  {Array}   cache_item_result  - Cache item result to add to summary information
 */
 
OpenAjax.a11y.FilteredCacheItemResults.prototype.updateSummary = function(cache_item_result) {

  this.result_summary.addResultSummary(cache_item_result.getResultSummary());

};

/**
 * @method getCacheItemResults
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Constructs a data structure of cache items associated with a rule category 
 *       The cache items returned can be filtered by the tpe of evaluation result 
 *          
 * @param  {Number}  element_type             - Number representing the element types to be included
 * @param  {Number}  filter        (optional) - Number representing the evaluation results filter (default all results)
 */

 OpenAjax.a11y.FilteredCacheItemResults.prototype.getCacheItemResults = function(element_type, filter) {

//  OpenAjax.a11y.logger.debug("FILTER: " + filter );

  var total;

  var ELEMENT_TYPE = OpenAjax.a11y.ELEMENT_TYPE;

  this.element_type  = element_type;
  
  if (typeof filter !== 'number') filter = OpenAjax.a11y.RESULT_FILTER.ALL;

  this.filter = filter;

  this.cache_item_results = [];
  
  var ci_result = null;
  
  switch (element_type) {
  
  case ELEMENT_TYPE.ALL:
    this.title = "All Elements";
    this.filterCacheItemsByNodeResultsFromTree(this.dom_cache.element_cache.child_dom_elements, filter, true);
    break;

  case ELEMENT_TYPE.AUDIO_VIDEO:
    this.title = "Audio and Video Elements";
    this.filterCacheItemsByNodeResultsFromTree(this.dom_cache.media_cache.media_elements, filter);
    break;

  case ELEMENT_TYPE.FORM_CONTROLS:
    this.title = "Form Control Elements";
    this.filterCacheItemsByNodeResultsFromTree(this.dom_cache.controls_cache.child_cache_elements, filter);    
    break;

  case ELEMENT_TYPE.HEADINGS_LANDMARKS:
    this.title = "Heading Elements";
    var cache =  this.dom_cache.headings_landmarks_cache;

    if (cache.title_element) { 
      ci_result = new OpenAjax.a11y.CacheItemResult(cache.title_element, filter, 1); 
      if (ci_result) this.cache_item_results.push(ci_result);
    }

    if (cache.page_element)  { 
      ci_result = new OpenAjax.a11y.CacheItemResult(cache.page_element, filter, 2); 
      if (ci_result) this.cache_item_results.push(ci_result);
    }
    
    this.filterCacheItemsByNodeResultsFromTree(cache.child_cache_elements, filter, 2);
    
    break;

  case ELEMENT_TYPE.IMAGES:
    this.title = "Image Elements";
    this.filterCacheItemsByNodeResultsFromList(this.dom_cache.images_cache.image_elements, filter);    
    break;

  case ELEMENT_TYPE.LINKS:
    this.title = "Link Elements";
    this.filterCacheItemsByNodeResultsFromList(this.dom_cache.links_cache.link_elements, filter);    
    break;

  case ELEMENT_TYPE.LAYOUT_TABLES:

    if (this.dom_cache.tables_cache.page_element)  { 
      ci_result = new OpenAjax.a11y.CacheItemResult(this.dom_cache.tables_cache.page_element, filter, 1); 
      if (ci_result) this.cache_item_results.push(ci_result);
    }
    
    this.title = "Table Elements";
    this.filterCacheItemsByNodeResultsFromTree(this.dom_cache.tables_cache.child_cache_elements, filter, 1);
    break;

  case ELEMENT_TYPE.TEXT:
    this.title = "Elements with Text Content";
    this.filterCacheItemsByNodeResultsFromList(this.dom_cache.text_cache.text_nodes, filter);
    break;

  case ELEMENT_TYPE.WIDGETS:
    this.title = "Widget Elements";
    this.filterCacheItemsByNodeResultsFromTree(this.dom_cache.controls_cache.child_cache_elements, filter);
    break;

  default:
    break;  
   
  }
  
};

/**
 * @method filterCacheItemsByNodeResultsFromList
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 * 
 * @desc Returns a lists of cache item results by filtered node results based on 
 *       the filter.  Does not traverse the children of the cache items
 *
 * @param  {Array}   cache_items  - List of cache element items
 * @param  {Number}  filter       - Number representing the types of results 
 *                                  to include in the array
 * @param  {Number}  start        - Start for the ordinal position
 */
 
OpenAjax.a11y.FilteredCacheItemResults.prototype.filterCacheItemsByNodeResultsFromList = function(cache_items, filter, start) {

  if (typeof start !== 'number') start = 0;

  var index = start + 1;

  this.is_tree = false;

  var RESULT_FILTER = OpenAjax.a11y.RESULT_FILTER;

  var cache_items_len = cache_items.length;
  
  for (var i = 0; i < cache_items_len; i++) {
  
    var ci = cache_items[i];
    
    var ci_result = new OpenAjax.a11y.CacheItemResult(ci, filter, index); 

    index++;

    this.updateSummary(ci_result);

    if (ci_result) this.cache_item_results.push(ci_result);

  } 
  
};  

 
/**
 * @method filterCacheItemsByNodeResultsFromTree
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 * 
 * @desc Returns an nested lists of cache item results by node results based on the filter 
 *
 * @param  {Array}    cache_items  - Array of cache element items
 * @param  {Number}   filter       - Number representing the types of results 
 *                                   to include in the array
 * @param  {Boolean}  as_list      - Optional parameter to force result to be a list
 * @param  {Number}   start        - Start for the ordinal position
 *
 * @return {Number}  Number of cache items that were not included due to filter settings
 */
 
OpenAjax.a11y.FilteredCacheItemResults.prototype.filterCacheItemsByNodeResultsFromTree = function(cache_items, filter, as_list, start) {

   function traverseCacheItems(cache_item_result, cache_item) {
  
    var ci_result = new OpenAjax.a11y.CacheItemResult(cache_item, filter, index);
    
    index++;
    
    filtered_cache_item_results.updateSummary(ci_result);

    if (cache_item_result && !as_list) {
      cache_item_result.addChildCacheItemResult(ci_result);
      is_tree = true;
    } else {
      cache_item_results.push(ci_result);
    } 

    var child_cache_elements     = [];
    var child_cache_elements_len = 0;
    
    if (cache_item.child_cache_elements) child_cache_elements = cache_item.child_cache_elements;
    else if (cache_item.child_dom_elements) child_cache_elements = cache_item.child_dom_elements;

    child_cache_elements_len = child_cache_elements.length;

//    OpenAjax.a11y.logger.debug("CI Result: " + ci_result + "   flag: " + OpenAjax.a11y.FilteredCacheItemResults.add_flag + "   children: " + child_cache_elements_len);

    for (var i = 0; i < child_cache_elements_len; i++) {
    
      var cces = child_cache_elements[i];
      traverseCacheItems(ci_result, cces);
      
    }
    
  }

  var RESULT_FILTER = OpenAjax.a11y.RESULT_FILTER;

  var is_tree = false;
  
  var filtered_cache_item_results = this;
  
  if (typeof as_list !== 'boolean') as_list = false;

  if (typeof start !== 'number') start = 0;

  var index = start + 1;

  var cache_item_results = [];

  var cache_items_len = cache_items.length;
  
  var all_flag = (this.filter === RESULT_FILTER.ALL);
  
  for (var i = 0; i < cache_items_len; i++) {
    var ci = cache_items[i];
    traverseCacheItems(null, ci);
  } 
  
  this.is_tree = is_tree;
  this.cache_item_results = cache_item_results;

//  OpenAjax.a11y.logger.debug("IS TREE: " + this.is_tree);
};  


/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Returns an JSON representation of the filtered cache item results 
 *
 * @param {String} prefix  -  A prefix string typically spaces
 *
 * @return  {String}  JSON string representing the report data 
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.toJSON = function(prefix, element_type_title) {

  var next_prefix = "";

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else next_prefix = prefix + "    ";  

  if (typeof  element_type_title !== 'string' ||  element_type_title.length === 0)  element_type_title = "no element title";

  var json = "";

  var ruleset_title   = this.evaluation_results.getRulesetTitle();
  var ruleset_version = this.evaluation_results.getRulesetVersion();
  var ruleset_id      = this.evaluation_results.getRulesetId();
  
  var eval_title = this.evaluation_results.getTitle();
  var eval_url   = this.evaluation_results.getURL();
  var date       = this.evaluation_results.getDate().split(':');
  var eval_time  = date[1] + ":" + date[2];
  var eval_date  = date[0];

  if (typeof eval_title != 'string' && eval_title.length === 0) eval_title = "no evaluation title";

  json += "{";

  json += prefix + "  \"element_type_title\"   : \"" + OpenAjax.a11y.util.escapeForJSON(element_type_title) + "\",";

  json += prefix + "  \"ruleset_title\"   : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset_title) + "\",";
  json += prefix + "  \"ruleset_version\" : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset_version) + "\",";
  json += prefix + "  \"ruleset_id\"      : \"" + ruleset_id + "\",";

  json += prefix + "  \"eval_title\"    : \"" + OpenAjax.a11y.util.escapeForJSON(eval_title) + "\",";
  json += prefix + "  \"eval_url\"      : \"" + OpenAjax.a11y.util.escapeForJSON(eval_url) + "\",";
  json += prefix + "  \"eval_date\"     : \"" + OpenAjax.a11y.util.escapeForJSON(eval_date) + "\",";
  json += prefix + "  \"eval_time\"     : \"" + OpenAjax.a11y.util.escapeForJSON(eval_time) + "\",";


  if (this.is_tree) json += prefix + "  \"is_tree\" : true,";
  else json += prefix + "  \"is_tree\" : false,";
  
  json += prefix + "  \"results\" : [";
  
  var ci_results = this.cache_item_results;
  var ci_results_len = ci_results.length;
  var ci_results_last = ci_results_len - 1;
  
//  OpenAjax.a11y.logger.debug("  Number of cache results: " + ci_results.length);

  for (var i = 0; i < ci_results_len; i++) {

//    OpenAjax.a11y.logger.debug("  " + i + ": " + ci_results[i].cache_item.cache_id);

    json += ci_results[i].toJSON(next_prefix);
    
    if (i !== ci_results_last) json += ",";
  }  
  json += prefix + "  ]";

  json += prefix + "}";
  
  return json; 

};

/**
 * @method toHTML
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Returns an HTML representation of the filtered cache item results 
 *
 * @param  {String}  title  -   Title of the report
 *
 * @return  {String}  String representing the HTML for the report 
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.toHTML = function(title) {

  var evaluation_results = this.evaluation_results;
  
  var wcag20_nls  = OpenAjax.a11y.all_wcag20_nls.getNLS();  

  var html = "";
  
  html += "<!DOCTYPE html>\n";
  html += "<html xml:lang='en' lang='en'>\n";
  html += "  <head>\n";
  html += "    <title>" + title + "</title>\n";
  html += "    <meta charset='ISO-8859-1' />\n";
  html += OpenAjax.a11y.report_css;
  html += "    <script type='text/javascript'>\n";
  html += "      var OAA_REPORT = {};\n";  
  html += "      OAA_REPORT.element_type_data = " + this.toJSON("\n      ", title) + ";\n\n";
  html += "      OAA_REPORT.ruleset = " + evaluation_results.toJSON("\n      ", evaluation_results.getRulesetTitle()) + ";\n\n";
  html += "      OAA_REPORT.wcag20  = " + wcag20_nls.toJSON("\n      ") + ";\n\n";
  html += "    </script>\n";
  html += OpenAjax.a11y.report_element_type_view_js;
  html += "  </head>\n";
  html += OpenAjax.a11y.report_element_type_view_body;
  html += "</html>\n";
  
  return html;
};

/**
 * @method toCSV
 *
 * @memberOf OpenAjax.a11y.FilteredCacheItemResults
 *
 * @desc Returns an CSV representation of the filtered cache item results 
 *
 * @param  {String}  title  -   Title of the report
 *
 * @return  {String}  String representing the CSV for the report 
 */

OpenAjax.a11y.FilteredCacheItemResults.prototype.toCSV = function(title) {

  var eval_title = this.evaluation_result.title;
  var eval_url   = this.evaluation_result.url;
  var date       = this.evaluation_result.date.split(':');
  var eval_time  = date[1] + ":" + date[2];
  var eval_date  = date[0];
  
  if (eval_title.length > 30) eval_title = eval_title.slice(0,27) + "...";
    
  var csv = title + "\n\n";

  csv += "\"OAA ID\"";  
  csv += ",\"Element Description\""; 
  csv += ",\"Element id attribute\""; 
  csv += ",\"Element class attribute\""; 
  csv += ",\"Parent Landmark\""; 
  csv += ",\"Rule ID\"";
  csv += ",\"Type\"";
  csv += ",\"WCAG 2.0 Success Criterion\"";
  csv += ",\"WCAG 2.0 Level\"";
  csv += ",\"Severity\"";
  csv += ",\"Evaluation Result Message\"";
  csv += ",\"Evaluation Date\"";
  csv += ",\"Evaluation Time\"";
  csv += "\"URL Evaluated\"";
  csv += ",\"Title of URL Evaluated\"";
  csv += "\n";

  var result_items     = this.cache_item_results;
  var result_items_len = result_items.length;

  for (var i = 0; i < result_items_len; i++) {
         
     var position = i+1;
            
     var result_item      = result_items[i];
     var node_results     = result_item.node_results;
     var node_results_len = node_results.length;
            
     for (var j = 0; j < node_results_len; j++) {
            
       var node_result = node_results[j];
       
       var dom_element = result_item.cache_item;
       
       if (typeof result_item.cache_item.dom_element ===  'object') dom_element = result_item.cache_item.dom_element;
       else dom_element = result_item.cache_item;
       
       var cache_item = node_result.getCacheItem();
               
       csv += "\"" + cache_item.cache_id; 
       csv += "\",\"" + OpenAjax.a11y.util.escapeForJSON(cache_item.toString()); 
       csv += "\",\"" + dom_element.getId(); 
       csv += "\",\"" + dom_element.getClassName(); 
       csv += "\",\"" + dom_element.getParentLandmark(); 
       csv += "\",\"" + node_result.getRuleIdNLS() + ": " + OpenAjax.a11y.util.escapeForJSON(node_result.getRuleSummary());
       csv += "\",\"" + node_result.getRuleType();
       csv += "\",\"" + node_result.getPrimarySuccessCriterion();
       csv += "\",\"" + node_result.getWCAG20Level();
       csv += "\",\"" + node_result.getResultValue().label;
       csv += "\",\"" + OpenAjax.a11y.util.escapeForJSON(node_result.getResultMessage());
       csv += "\",\"" + eval_date;
       csv += "\",\"" + eval_time;
       csv += "\",\"" + OpenAjax.a11y.util.escapeForJSON(eval_title);
       csv += "\",\"" + eval_url;
       csv += "\"\n";
               
    }
  }   

  return csv;
};


/* ---------------------------------------------------------------- */
/*                           CacheItemResult                        */
/* ---------------------------------------------------------------- */

/**
 * @constructor CacheItemResult
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Represents one of the cache items in a cache items filter result object
 *          
 * @param  {CacheItem}  cache_item  - cache item to be included in filtered results
 * @param  {Number}     filter      - Number representing the types of results 
 *                                    to include in the array
 * @param  {Number}     pos         - Position in a list
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property {Object}  cache_item  - Cache item object
 *
 * @property {Number}  ordinal_position      - Position in a list
 *
 * @property {Array}   filtered_node_results - Filtered node results of the cache item 
 *
 * @property  {SummaryResult}  summary_result - Summary of the node results for 
 *                                            the cache item  
 *
 * @property {Number}  number_of_node_results_filtered  - Number of node results that 
 *                                            have been filtered from the list
 *
 * @property {Array}   children  - Array of cache item result objects  (if there
 *                                 are parent/child relationships, like headings
 *                                 and landmarks)
 */

 OpenAjax.a11y.CacheItemResult = function(cache_item, filter, pos) {

  function addNodeResultsPage(node_results) {  
  
    var node_results_len = node_results.length;
    var count = 0;
    
    for (var i = 0; i < node_results_len; i++) {
      var node_result = node_results[i];
      if (node_result.isScopePage()) {
        filtered_node_results.push(node_result);
        count++;
      }  
    }
    
    return count;
  }

  function addNodeResultsElement(node_results) {  
  
    var node_results_len = node_results.length;
    var count = 0;
    
    for (var i = 0; i < node_results_len; i++) {
      var node_result = node_results[i];
      if (node_result.isScopeElement()) {
        filtered_node_results.push(node_result);
        count++;
      }  
    }
    
    return count;
  }

  function addNodeResults(node_results) {  
  
    var node_results_len = node_results.length;
    
    for (var i = 0; i < node_results_len; i++) filtered_node_results.push(node_results[i]);
    
    return node_results_len;
  }


  this.cache_item = cache_item;
  
  this.filtered_node_results     = [];

  this.number_of_node_results_filtered = 0;

  this.is_tree = false;

  this.ordinal_position = pos;
  
  var rs = new OpenAjax.a11y.ResultSummary();
  
  this.children = [];

  var RESULT_FILTER = OpenAjax.a11y.RESULT_FILTER;
  
  var filtered_node_results = this.filtered_node_results;
  
  var de = cache_item;
  if (typeof cache_item.dom_element  != 'undefined') de = cache_item.dom_element;
  
  if (RESULT_FILTER.VIOLATION            & filter) rs.addViolations(addNodeResults(de.rules_violations));
  if (RESULT_FILTER.PAGE_MANUAL_CHECK    & filter) rs.addManualChecks(addNodeResultsPage(de.rules_manual_checks));
  if (RESULT_FILTER.ELEMENT_MANUAL_CHECK & filter) rs.addManualChecks(addNodeResultsElement(de.rules_manual_checks));
  if (RESULT_FILTER.WARNING              & filter) rs.addWarnings(addNodeResults(de.rules_warnings));
  if (RESULT_FILTER.PASS                 & filter) rs.addPassed(addNodeResults(de.rules_passed));
  if (RESULT_FILTER.HIDDEN               & filter) rs.addHidden(addNodeResults(de.rules_hidden));     
  
  this.number_of_node_results_filtered  = (de.rules_passed.length        - rs.passed);
  this.number_of_node_results_filtered += (de.rules_violations.length    - rs.violations);
  this.number_of_node_results_filtered += (de.rules_warnings.length      - rs.warnings);
  this.number_of_node_results_filtered += (de.rules_manual_checks.length - rs.manual_checks);
  this.number_of_node_results_filtered += (de.rules_hidden.length        - rs.hidden);

  this.result_summary = rs;
      

};

/**
 * @method addChildCacheItemResult
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Adds a cache item result to the children list of a cache item result object 
 *          
 * @param  {CacheItem Object}  cache_item  - cache item to be included in filtered results
 */

OpenAjax.a11y.CacheItemResult.prototype.addChildCacheItemResult = function(cache_item) {

  if (cache_item) { 
    this.children.push(cache_item);
    this.is_tree = true;
  }  

};


/**
 * @method isTree
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Tests if the cache items has a tree structure (otherwise simple list)
 *
 * @return  {Boolean}  true if cache items are organized as a tree, otherwise false
 */

OpenAjax.a11y.CacheItemResult.prototype.isTree = function() {

  return this.is_tree;
};

/**
 * @method agetCacheItem
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Get cache item object 
 *          
 * @return  {Object}  Cache item object
 */

OpenAjax.a11y.CacheItemResult.prototype.getCacheItem = function() {

  return this.cache_item;

};


/**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Gets numerical summary information about the cache item results 
 *
 * @return {ResultSummary} Returns the ResultSummary object 
 *
 */
 
OpenAjax.a11y.CacheItemResult.prototype.getResultSummary = function () {

  return this.result_summary;

};

/**
 * @method hasResults
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Tests if node has any rule results
 *          
 * @param  {Boolean}  True if there are rule results, otherwise false
 */

OpenAjax.a11y.CacheItemResult.prototype.hasResults = function() {

  return this.result_summary.hasResults();

};

/**
 * @method getHighestResultValueConstant
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Gets highest result value for the node results for this cache item
 *
 * @return {Number} Number representing the highest result value 
 */
 
OpenAjax.a11y.CacheItemResult.prototype.getHighestResultValueConstant = function () {

  var RESULT_VALUE =  OpenAjax.a11y.RESULT_VALUE;
  
  var rs = this.getResultSummary();
  
  if (rs.violations)    return RESULT_VALUE.VIOLATION;
  if (rs.warnings)      return RESULT_VALUE.WARNING;
  if (rs.manual_checks) return RESULT_VALUE.MANUAL_CHECK;
  if (rs.passed)        return RESULT_VALUE.PASS;
  if (rs.hidden)        return RESULT_VALUE.HIDDEN;
  
  return RESULT_VALUE.NONE;

};


/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Gets an array of node results for the cache item 
 *          
 * @param  {Array}  Array of node results 
 */

OpenAjax.a11y.CacheItemResult.prototype.getNodeResults = function() {

  return this.filtered_node_results;

};

/**
 * @method getOrdinalPosition
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Gets ordinal position of the cache item in the DOM, 1 based
 *          
 * @param  {Number}  Number representing the ordinal position
 */

OpenAjax.a11y.CacheItemResult.prototype.getOrdinalPosition = function() {

  return this.ordinal_position;

};


/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.CacheItemResult
 *
 * @desc Returns a JSON representation of the cache item 
 *          
 * @param {String} prefix  -  A prefix string typically spaces
 *
 * @return  {String}  String representing the cache item result object
 */

OpenAjax.a11y.CacheItemResult.prototype.toJSON = function(prefix) {

  var next_prefix = "";
  var next_prefix_2 = "";

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else {
    next_prefix = prefix + "  ";  
    next_prefix_2 = next_prefix + "  ";  
  }  

  var i;
  var json = "";
  
  var rs = this.getResultSummary();
  
  json += prefix + "{ \"cache_item\"    : \"" + OpenAjax.a11y.util.escapeForJSON(this.cache_item.toString()) + "\",";
  json += prefix + "  \"cache_id\"      : \"" + this.cache_item.cache_id + "\",";
  json += prefix + "  \"violations\"    : "  + rs.violations    + ",";
  json += prefix + "  \"manual_checks\" : "  + rs.manual_checks + ",";
  json += prefix + "  \"warnings\"      : "  + rs.warnings      + ",";
  json += prefix + "  \"passed\"        : "  + rs.passed        + ",";
  json += prefix + "  \"hidden\"        : "  + rs.hidden        + ",";
  json += prefix + "  \"total\"         : "  + rs.total         + ",";
  
  json += prefix + "  \"filtered\"      : "  + this.number_of_node_results_filtered  + ",";

  if (this.filtered_node_results.length > 0) {
    json += prefix + "  \"node_results\" : [";
    
    var n_results      = this.filtered_node_results;
    var n_results_len  = n_results.length;
    var n_results_last = n_results_len - 1;
    
    for (i = 0; i < n_results_len; i++) {
      json += n_results[i].toJSON(next_prefix);
      if (i !== n_results_last) json += ","; 
    }  
    json += prefix + "  ],";
  }
  else {
    json += prefix + "  \"node_results\" : [],";
  }

  if (this.children.length > 0) {
    json += prefix + "  \"children\" : [";
    
    var children      = this.children;
    var children_len  = children.length;
    var children_last = children_len - 1;
    
    for (i = 0; i < children_len; i++) {
      json += children[i].toJSON(next_prefix_2);
      if (i !== children_last) json += ','; 
    }  
    json += prefix + "  ]";
  }
  else {  
    json += prefix + "  \"children\" : []";
  }
  
  json += prefix + "}";

  return json;

};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                      FilteredRuleResultsGroups                    */
/* ---------------------------------------------------------------- */

/**
 * @constructor FilteredRuleResultsGroups
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructs a data structure of cache items associated with a rule category 
 *       Node results can be filtered when a rule result is added to the group 
 *
 * @param  {Object}  evaluation_result  - evaluation results used to generate 
 *                                        the filtered results
 * @param  {String}  group_id  - id used to identify this grouping of rules
 *                               
 * @param  {String}  title     - Title for the group 
 * @param  {String}  url       - URL to more information on the group  
 * @param  {String}  desc      - Description of the group 
 *
 * @property  {EvaluationResult} evaluation_result - ruleset and evaluation results 
 *                                                   used to generate the filtered results
 *
 * @property  {String}  group_id  - ID to uniquely identify this group among other 
 *                                  grouping objects
 *
 * @property  {Array}   filtered_rule_results   - array of filtered rule result objects 
 *
 * @property  {Array}   filtered_node_results   - array of node results for the group
 *
 * @property  {String}  title - title for the group of rules
 * @property  {String}  url   - Optional property to provide a link to more information 
 *                              about a group
 * @property  {String}  desc  - Optional property to provide a description of the group
 *
 * @property  {Number}  node_results_filtered_out    - number of node results filtered
 *
 * @property  {Object}  group_information     - Information on the number and types of rules
 *
 * @property  {Boolean}  has_rules    - True if group contains at least one rule 
 *
 * @property  {ResultSummary}  result_summary  - Summary of the node results for 
 *                                               the rule  
 */

 OpenAjax.a11y.FilteredRuleResultsGroups = function(eval_result, group_id, title, url, desc) {

  this.evaluation_result = eval_result;
  this.group_id = group_id;

  this.filtered_rule_results_groups = [];

  this.filtered_node_results = [];

  this.title = "";
  this.url = "";
  this.desc = "";
  
  if (typeof title === 'string') this.title = title;
  else this.title = "No title";
  if (typeof url  === 'string') this.url = url;
  if (typeof desc === 'string') this.desc = desc;

  this.has_rules = false;

  this.node_results_filtered_out   = 0;

  this.result_summary = new OpenAjax.a11y.ResultSummary();  
  
  this.group_information = new OpenAjax.a11y.info.RuleResultsGroupInfo(title, url, desc);
    
}; 

/**
 * @method getRulesetInfo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Return ruleset of information 
 *
 * @return {RulesetInfo}  RulesetInfo object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getRulesetInfo = function () {
  return this.evaluation_result.getRulesetInfo();
};

/**
 * @method getPageInfo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Return information on the web page evaluated 
 *
 * @return {PageInfo}  PageInfo object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getPageInfo = function () {
  return this.evaluation_result.getPageInfo();
};

/**
 * @method getRuleResultsGroupInfo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Return information on the web page evaluated 
 *
 * @return {PageInfo}  PageInfo object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getRuleResultsGroupInfo = function () {
  return this.group_information;
};



/**
 * @method getTitle
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns the title of the group
 *
 * @return  {String} String representing the title of the group
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getTitle = function() {

  return this.title;
};

/**
 * @method getURL
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns the url of the group, can be empty
 *
 * @return  {String} String of the url to more information about a group
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getURL = function() {

  return this.url;
};

/**
 * @method getDescription
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns the description of the group, can be empty
 *
 * @return  {String} String describing the group of rules
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getDescription = function() {

  return this.desc;
};

/**
 * @method getNumberOfRequiredRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Get number of required rules in the groups
 *
 * @return  {Number} Number of required rules
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getNumberOfRequiredRules = function() {

  return this.group_information.required_rules;
};

/**
 * @method getNumberOfRecommendedRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Get number of recommended rules in the groups
 *
 * @return  {Number} Number of recommended rules
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getNumberOfRecommendedRules = function() {

  return this.group_information.recommended_rules;
};

/**
 * @method hasResults
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Tests if any of the rules in this group applied to the content in the page 
 *
 * @return {Boolean} True if any of the rule have results, otherwise false
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.hasResults = function () {

   return this.result_summary.hasResults();

};

/**
 * @method hasRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Tests if any of their are any rules in this group  
 *
 * @return {Boolean} True if the group contains at least one rule, otherwise false
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.hasRules = function () {

   return this.has_rules;

};


 /**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Gets numerical summary information about the rule results 
 *
 * @return {ResultSummary} Returns the ResultSummary object 
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getResultSummary = function () {

  return this.result_summary;

};

/**
 * @method addFilteredRuleResultsGroup
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Adds a filtered rule result group to this group for aggregating results
 *
 * @param  {FilteredRuleResultGroup}  filtered_rule_results_group  - Filtered rule result group object to add
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.addFilteredRuleResultsGroup = function(filtered_rule_results_group) {

  if (filtered_rule_results_group) {
    this.filtered_rule_results_groups.push(filtered_rule_results_group);
  }
  
};

/**
 * @method addRuleResult
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Adds a rule result to the grouping aggregation of results if the group id has a match in the group
 *
 * @param  {String}      group_id      - id used for matching the rule result object with a filtered rule result group
 * @param  {RuleResult}  rule_result   - Filtered rule result object to aggregate
 * @param  {Number}      filter        - Filter for node results (bit mapped mask)
 *
 * @return  {FilteredRuleResult | object}  Returns a filtered rule result object if rule set was added to this group, 
 *                                         otherwise an return object with an added property   
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.addRuleResult = function(group_id, rule_result, filter) {

  function checkForNewNodes(old_list, new_list) {
  
    for (var i = 0; i < new_list.length; i++) {
    
      var item = new_list[i].cache_item;
    
      if (item.dom_element) item = item.dom_element.cache_id;
      else if (item.parent_element) item = item.parent_element.cache_id;
      else item = 'unknown';

      if (old_list.indexOf(item) === -1) old_list.push(item);
    
    }

    return old_list;
  
  }


  var RETURN_VALUE = OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE;

  var groups = this.filtered_rule_results_groups;
  var groups_len = groups.length;

  for (var i = 0; i < groups_len; i++) {
  
     var group = groups[i];

//     OpenAjax.a11y.logger.debug("GROUP: " + group.getTitle());

     var filtered_rule_result = group.addRuleResult(group_id, rule_result, filter);
     
     if (filtered_rule_result.added === RETURN_VALUE.ADDED) {

       this.has_rules = true;

//       OpenAjax.a11y.logger.debug("  Adding rule: " + rule_result.getRuleIdNLS() + " to " + group.getTitle());

       this.filtered_node_results  = checkForNewNodes(this.filtered_node_results, rule_result.node_results_passed);
       this.filtered_node_results  = checkForNewNodes(this.filtered_node_results, rule_result.node_results_violations);
       this.filtered_node_results  = checkForNewNodes(this.filtered_node_results, rule_result.node_results_warnings);
       this.filtered_node_results  = checkForNewNodes(this.filtered_node_results, rule_result.node_results_manual_checks);

       if (rule_result.rule_mapping.required) this.group_information.addToRequiredCount(1);

       this.node_results_filtered_out     += filtered_rule_result.node_results_filtered_out;

       var rs = filtered_rule_result.getResultSummary();

       this.result_summary.addPassed(rs.passed);        
       this.result_summary.addViolations(rs.violations);
       this.result_summary.addWarnings(rs.warnings);
       this.result_summary.addManualChecks(rs.manual_checks);        
       this.result_summary.addHidden(rs.hidden);        
        
        
       return filtered_rule_result;
     }

  }

  var ro = {};
  ro.added = RETURN_VALUE.NO_MATCH;

  return ro; 

};


/**
 * @method getNumberOfRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns number of rules in this group
 *
 * @return  {Number}  Number of rules in this group 
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getNumberOfRules = function() {

  return this.group_information.total_rules;

};


/**
 * @method getDocumentObject
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Get the document object evaluated
 *
 * @return {Object}  Document object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroups.prototype.getDocumentObject = function () {
  return this.evaluation_result.getDocumentObject();
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns an JSON representation of the rule summary results 
 *
 * @param {String} prefix  -  A prefix string typically spaces
 *
 * @return  {String}  JSON string representing the report data 
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.toJSON = function(prefix) {

  var next_prefix = "";

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else next_prefix = prefix + "    ";  

  var group_title = this.title;
  var group_id    = this.group_id;
  
  var ruleset_title   = this.evaluation_result.ruleset_title;
  var ruleset_version = this.evaluation_result.ruleset_version;
  var ruleset_id      = this.evaluation_result.ruleset_id;
  
  var eval_title = this.evaluation_result.title;
  var eval_url   = this.evaluation_result.url;
  var date       = this.evaluation_result.date.split(':');
  var eval_time  = date[1] + ":" + date[2];
  var eval_date  = date[0];

  var json = "";
  
  json += prefix + "{";

  var gi = this.group_information;

  json += prefix + "  \"group_title\"  : \"" + OpenAjax.a11y.util.escapeForJSON(gi.title) + "\",";
  json += prefix + "  \"group_id\"     : \"" + gi.id + "\",";
  
  json += prefix + "  \"ruleset_title\"   : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset_title) + "\",";
  json += prefix + "  \"ruleset_version\" : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset_version) + "\",";
  json += prefix + "  \"ruleset_id\"      : \"" + ruleset_id + "\",";

  json += prefix + "  \"eval_title\"    : \"" + OpenAjax.a11y.util.escapeForJSON(eval_title) + "\",";
  json += prefix + "  \"eval_url\"      : \"" + OpenAjax.a11y.util.escapeForJSON(eval_url)   + "\",";
  json += prefix + "  \"eval_date\"     : \"" + OpenAjax.a11y.util.escapeForJSON(eval_date)  + "\",";
  json += prefix + "  \"eval_time\"     : \"" + OpenAjax.a11y.util.escapeForJSON(eval_time)  + "\",";
  
  json += prefix + "  \"nodes_filtered\"   : \"" + this.node_results_filtered_out   + "\",";
  json += prefix + "  \"required_rules\"   : \"" + gi.required_rules    + "\",";
  json += prefix + "  \"recommened_rules\" : \"" + gi.recommended_rules + "\",";

  json += prefix + "  \"has_rules\"   : \"" + this.has_rules   + "\",";

  var rs = this.result_summary;

  json += prefix + "  \"has_results\" : \"" + rs.hasResults()   + "\",";
  json += prefix + "  \"percent\"     : \"" + rs.percent_passed + "\",";
  json += prefix + "  \"passed\"      : \"" + rs.passed         + "\",";
  json += prefix + "  \"violations\"  : \"" + rs.violations     + "\",";
  json += prefix + "  \"warnings\"    : \"" + rs.warnings       + "\",";
  json += prefix + "  \"total\"       : \"" + rs.total          + "\",";
  json += prefix + "  \"manual\"      : \"" + rs.manual_checks  + "\",";
  json += prefix + "  \"hidden\"      : \"" + rs.hidden         + "\",";
  
  json += prefix + "  \"summary_results\" : [";
  
  var results = this.filtered_rule_results_groups;
  var results_len = results.length;
  var results_comma = results_len - 1;
  
  for (var i = 0; i < results_len; i++) {
    json += results[i].toJSON(prefix + "  ", false); 
    if (i < results_comma) json += ","; 
  }  
  
  
  json += prefix + "  ]\n";

  json += prefix + "}";

  return json; 

};


/**
 * @method toHTML
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns an HTML representation of summary results 
 *
 * @param  {String}  title  -   Title of the report
 *
 * @return  {String}  String representing the HTML for the report 
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.toHTML = function(title) {

  var html = "";
  
  html += "<!DOCTYPE html>\n";
  html += "<html lang='en'>\n";
  html += "  <head>\n";
  html += "    <title>" + title + "</title>\n";
  html += "    <meta charset='ISO-8859-1' />\n";
  html += OpenAjax.a11y.report_css;
  html += "    <script type='text/javascript'>\n";
  html += "      var OAA_REPORT = {};\n";  
  html += "      OAA_REPORT.title = '" + OpenAjax.a11y.util.escapeForJSON(title) + "';\n";  
  html += "      OAA_REPORT.rule_summary_data = " + this.toJSON("\n      ") + ";\n\n";
  html += "    </script>\n";
  html += OpenAjax.a11y.report_rule_summary_view_js;
  html += "  </head>\n";
  html += OpenAjax.a11y.report_rule_summary_view_body;
  html += "</html>\n";
  
  return html;
  
};

/**
 * @method toCSV
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroups
 *
 * @desc Returns an CSV representation of summary results 
 *
 * @param  {String}   title  -   Title of the report
 * @param  {Boolean}  header_flag  -  Optional, set to true if do not want headers for children
 *
 * @return  {String}  String representing the CSV for the report 
 */

OpenAjax.a11y.FilteredRuleResultsGroups.prototype.toCSV = function(title, header_flag) {

  var eval_title = this.evaluation_result.title;
  var eval_url   = this.evaluation_result.url;
  var date       = this.evaluation_result.date.split(':');
  var eval_time  = date[1] + ":" + date[2];
  var eval_date  = date[0];
  
  if (eval_title.length > 30) eval_title = eval_title.slice(0,27) + "...";
    
  var csv = "";
  
  if ((typeof header_flag !== 'boolean') || header_flag) {
    csv += title + "\n\n";
    csv += "\"OAA ID\"";  
    csv += ",\"Element Description\""; 
    csv += ",\"Element id attribute\""; 
    csv += ",\"Element class attribute\""; 
    csv += ",\"Parent Landmark\""; 
    csv += ",\"Rule ID\"";
    csv += ",\"Type\"";
    csv += ",\"WCAG 2.0 Success Criterion\"";
    csv += ",\"WCAG 2.0 Level\"";
    csv += ",\"Severity\"";
    csv += ",\"Evaluation Result Message\"";
    csv += ",\"Evaluation Date\"";
    csv += ",\"Evaluation Time\"";
    csv += "\"URL Evaluated\"";
    csv += ",\"Title of URL Evaluated\"";
    csv += "\n";
  }  

  var results = this.filtered_rule_results_groups;
  var results_len = results.length;
  var results_comma = results_len - 1;
  
  for (var i = 0; i < results_len; i++) {
    csv += results[i].toCSV(title, false); 
  }  

  return csv;  
};



/* ---------------------------------------------------------------- */
/*                      FilteredRuleResultsGroup                    */
/* ---------------------------------------------------------------- */


/**
 * @constructor FilteredRuleResultsGroup
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructs a data structure of cache items associated with a rule category 
 *       Node results can be filtered when a rule result is added to the group 
 *
 * @param  {Object}  evaluation_result  - ruleset and evaluation results used to generate 
 *                              the filtered results
 * @param  {String}  group_id  - id used to identify this grouping of rules and filtering rules
 *
 * @param  {String}  title     - Title for the group 
 * @param  {String}  url       - URL to more information on the group  
 * @param  {String}  desc      - Description of the group 
 *
 * @property  {EvaluationResult} evaluation_result - ruleset and evaluation results 
 *                                                   used to generate the filtered 
 *                                                   results
 *
 * @property  {String}  group_id  - ID to uniquely identify this group among other 
 *                                  grouping objects
 *
 * @property  {Array}   filtered_rule_results   - array of filtered rule result objects 
 *
 * @property  {Array}   filtered_node_results   - array of node results for the group
 *
 * @property  {String}  title  - title for the group of rules
 * @property  {String}  url    - Optional property to provide a link to more information about a group
 * @property  {String}  desc   - Optional property to provide a description of the group
 *
 * @property  {Number}  node_results_filtered_out    - number of node results filtered
 *
 * @property  {Object}  group_information     - Information on rules in the group
 *
 * @property  {Boolean}  has_rules    - True if group contains at least one rule 
 *
 * @property  {ResultSummary}  result_summary  - Summary of the node results for 
 *                                               the filtered rule results group  
 */


OpenAjax.a11y.FilteredRuleResultsGroup = function(eval_result, group_id, title, url, desc) {

  this.evaluation_result = eval_result;
  this.group_id = group_id;

  this.filtered_rule_results = [];

  this.filtered_node_results = [];

  this.title = "";
  this.url = "";
  this.desc = "";
  
  if (typeof title === 'string') this.title = title;
  else this.title = "No title";
  if (typeof url   === 'string') this.url   = url;
  if (typeof desc  === 'string') this.desc  = desc;
  
  this.has_rules = false;
  
  this.node_results_filtered_out = 0;

  this.result_summary = new OpenAjax.a11y.ResultSummary();  
  
  this.group_information = new OpenAjax.a11y.info.RuleResultsGroupInfo(title, url, desc);  

};

/**
 * @method getRulesetInfo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Return ruleset of information 
 *
 * @return {RulesetInfo}  RulesetInfo object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getRulesetInfo = function () {
  return this.evaluation_result.getRulesetInfo();
};

/**
 * @method getPageInfo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Return information on the web page evaluated 
 *
 * @return {PageInfo}  PageInfo object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getPageInfo = function () {
  return this.evaluation_result.getPageInfo();
};

/**
 * @method getRuleResultsGroupInfo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Return information on the web page evaluated 
 *
 * @return {PageInfo}  PageInfo object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getRuleResultsGroupInfo = function () {
  return this.group_information;
};




/**
 * @method getTitle
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns the title of the group
 *
 * @return  {String} String representing the title of the group
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getTitle = function() {

  return this.title;
};

/**
 * @method getURL
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns the url of the group, can be empty
 *
 * @return  {String} String of the url to more information about a group
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getURL = function() {

  return this.url;
};

/**
 * @method getDescription
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns the description of the group, can be empty
 *
 * @return  {String} String describing the group of rules
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getDescription = function() {

  return this.desc;
};

/**
 * @method getNumberOfRequiredRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Get number of required rules in the group
 *
 * @return  {Number} Number of required rules
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getNumberOfRequiredRules = function() {

  return this.group_information.required_rules;
};

/**
 * @method getNumberOfRecommendedRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Get number of recommended rules in the group
 *
 * @return  {Number} Number of recommended rules
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getNumberOfRecommendedRules = function() {

  return this.group_information.recommended_rules;
};


/**
 * @method hasResults
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Tests if any of the rules in this group applied to the content in the page 
 *
 * @return {Boolean} True if any of the rule have results, otherwise false
 */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.hasResults = function () {

   return this.result_summary.hasResults();

};

/**
 * @method hasRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Tests if any of their are any rules in this group  
 *
 * @return {Boolean} True if the group contains at least one rule, otherwise false
 */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.hasRules = function () {

   return this.has_rules;

};


/**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Gets numerical summary information about the rule results 
 *
 * @return {ResultSummary} Returns the ResultSummary object  */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getResultSummary = function () {

  return this.result_summary;

};


/**
 * method addRuleResult
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Adds a rule result to the grouping aggregation of results if the group id has a match in the group
 *
 * @param  {String}      group_id      - id used for matching the rule result object with a filtered rule result group
 * @param  {RuleResult}  rule_result   - Filtered rule result object to aggregate
 * @param  {Number}      filter        - Filter for node results (bit mapped mask)
 *
 * @return  {FilteredRuleResult | object}  Returns a filtered rule result object if rule set was added to this group, 
 *                                         otherwise an return object with an added property   
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.addRuleResult = function(group_id, rule_result, filter) {

  function checkForNewNodes(old_list, new_list) {
  
    for (var i = 0; i < new_list.length; i++) {
    
      var item = new_list[i].cache_item;
    
      if (item.dom_element) item = item.dom_element.cache_id;
      else if (item.parent_element) item = item.parent_element.cache_id;
      else item = 'unknown';

      if (old_list.indexOf(item) === -1) old_list.push(item);
    
    }

    return old_list;
  
  }

  var ro = {};
  
  var RETURN_VALUE = OpenAjax.a11y.FILTERED_RULE_RESULT_RETURN_VALUE;

//  OpenAjax.a11y.logger.debug("Group ID: " + this.group_id + "  Item ID: " + group_id );  

  if ((this.group_id === group_id) || 
      (typeof this.group_id === 'number' && 
       typeof group_id === 'number' && 
       (this.group_id & group_id))) {

    this.has_rules = true;

//    OpenAjax.a11y.logger.debug("  Adding rule result to group: " + rule_result + "  has rules: " + this.has_rules );  

    this.filtered_node_results = checkForNewNodes(this.filtered_node_results, rule_result.node_results_passed);
    this.filtered_node_results = checkForNewNodes(this.filtered_node_results, rule_result.node_results_violations);
    this.filtered_node_results = checkForNewNodes(this.filtered_node_results, rule_result.node_results_warnings);
    this.filtered_node_results = checkForNewNodes(this.filtered_node_results, rule_result.node_results_manual_checks);
    
    var filtered_rule_result = new OpenAjax.a11y.FilteredRuleResult(rule_result);
      
    filtered_rule_result.getFilteredNodeResults(filter);

    this.filtered_rule_results.push(filtered_rule_result);
    
    if (rule_result.rule_mapping.required) this.group_information.addToRequiredCount(1);
    else this.group_information.addToRecommendedCount(1);

    this.node_results_filtered_out     += filtered_rule_result.node_results_filtered_out;

    var rs = filtered_rule_result.getResultSummary();

//    OpenAjax.a11y.logger.debug(" Group: " + this.group_id + "  Rule " + rule_result.getRuleSummary() + " Total manual checks: " + rs.manual_checks + " New Manual Checks:  " + rs.manual_checks + " manual checks");  

    this.result_summary.addPassed(rs.passed);
    this.result_summary.addViolations(rs.violations);
    this.result_summary.addWarnings(rs.warnings);
    this.result_summary.addManualChecks(rs.manual_checks);    
    this.result_summary.addHidden(rs.hidden);

    filtered_rule_result.added = RETURN_VALUE.ADDED;
    
    return filtered_rule_result;
    
  }

  ro.added = RETURN_VALUE.NO_MATCH; 

//  OpenAjax.a11y.logger.debug("  NO MATCH");  

  return ro;

};

/**
 * @method getNumberOfRules
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns number of rules in this group
 *
 * @return  {Number}  Number of rules in this group 
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getNumberOfRules = function() {

  return this.group_information.total_rules;

};


/**
 * @method getDocumentObject
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Get the document object evaluated
 *
 * @return {Object}  Document object
 */
 
OpenAjax.a11y.FilteredRuleResultsGroup.prototype.getDocumentObject = function () {
  return this.evaluation_result.getDocumentObject();
};


/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns an JSON representation of the rule category results 
 *
 * @param {String}  prefix           -  A prefix string typically spaces
 * @param {Boolean} flag (optional)  -  True (default) to include filtered node results, false to not include 
 *
 * @return  {String}  JSON string representing the report data 
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.toJSON = function(prefix, flag) {

  if (typeof flag !== 'boolean') flag = true;
    
  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else next_prefix = prefix + "    ";  
  
  var group_title = this.title;
  var group_url   = this.url;
  var group_id    = this.group_id;
  
  var ruleset_title   = this.evaluation_result.ruleset_title;
  var ruleset_version = this.evaluation_result.ruleset_version;
  var ruleset_id      = this.evaluation_result.ruleset_id;
  
  var eval_title = this.evaluation_result.title;
  var eval_url   = this.evaluation_result.url;
  var date       = this.evaluation_result.date.split(':');
  var eval_time  = date[1] + ":" + date[2];
  var eval_date  = date[0];

  var json = "";
  
  json += prefix + "{";
  
  json += prefix + "  \"group_title\"   : \"" + OpenAjax.a11y.util.escapeForJSON(group_title) + "\",";
  json += prefix + "  \"group_url\"     : \"" + OpenAjax.a11y.util.escapeForJSON(group_url)   + "\",";
  json += prefix + "  \"group_id\"      : \"" + group_id + "\",";
  
  json += prefix + "  \"ruleset_title\"   : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset_title)   + "\",";
  json += prefix + "  \"ruleset_version\" : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset_version) + "\",";
  json += prefix + "  \"ruleset_id\"      : \"" + ruleset_id + "\",";

  json += prefix + "  \"eval_title\"    : \"" + OpenAjax.a11y.util.escapeForJSON(eval_title) + "\",";
  json += prefix + "  \"eval_url\"      : \"" + OpenAjax.a11y.util.escapeForJSON(eval_url)   + "\",";
  json += prefix + "  \"eval_date\"     : \"" + OpenAjax.a11y.util.escapeForJSON(eval_date)  + "\",";
  json += prefix + "  \"eval_time\"     : \"" + OpenAjax.a11y.util.escapeForJSON(eval_time)  + "\",";

  json += prefix + "  \"nodes_filtered\"   : \"" + this.node_results_filtered_out   + "\",";
  json += prefix + "  \"required_rules\"   : \"" + this.group_information.required_rules    + "\",";
  json += prefix + "  \"recommened_rules\" : \"" + this.group_information.recommended_rules + "\",";
  
  json += prefix + "  \"has_results\"      : \"" + this.has_results + "\",";
  json += prefix + "  \"has_rules\"        : \"" + this.has_rules   + "\",";

  var rs = this.result_summary;

  json += prefix + "  \"percent\"    : \"" + rs.percent_passed + "\",";
  json += prefix + "  \"passed\"     : \"" + rs.passed         + "\",";
  json += prefix + "  \"violations\" : \"" + rs.violations     + "\",";
  json += prefix + "  \"warnings\"   : \"" + rs.warnings       + "\",";
  json += prefix + "  \"total\"      : \"" + rs.total          + "\",";
  json += prefix + "  \"manual\"     : \"" + rs.manual_checks  + "\",";
  json += prefix + "  \"hidden\"     : \"" + rs.hidden         + "\",";
  
  json += prefix + "  \"rule_results\" : [";
  
  var results     = this.filtered_rule_results;
  var results_len = results.length;
  var comma_len   = results_len - 1;

  for (var i = 0; i < results_len; i++) {
    json += results[i].toJSON(prefix + "  ", flag); 
    if (i < comma_len) json += ",";
  }  
    
  json += prefix + "  ]\n";

  json += "}";

  return json; 

};


/**
 * @method toHTML
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns an HTML representation of rule category results 
 *
 * @param  {String}  title  -   Title of the report
 *
 * @return  {String}  String representing the HTML for the report 
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.toHTML = function(title) {

  var html = "";
  
  html += "<!DOCTYPE html>\n";
  html += "<html lang='en'>\n";
  html += "  <head>\n";
  html += "    <title>" + title + "</title>\n";
  html += "    <meta charset='ISO-8859-1' />\n";
  html += OpenAjax.a11y.report_css;
  html += "    <script type='text/javascript'>\n";
  html += "      var OAA_REPORT = {};\n";  
  html += "      OAA_REPORT.title = '" + OpenAjax.a11y.util.escapeForJSON(title) + "';\n";  
  html += "      OAA_REPORT.rule_category_data = " + this.toJSON("\n      ") + ";\n\n";
  html += "    </script>\n";
  html += OpenAjax.a11y.report_rule_category_view_js;
  html += "  </head>\n";
  html += OpenAjax.a11y.report_rule_category_view_body;
  html += "</html>\n";
  
  return html;
  
};

/**
 * @method toCSV
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResultsGroup
 *
 * @desc Returns an CSV representation of the filtered cache item results 
 *
 * @param  {String}  title  -   Title of the report
 *
 * @return  {String}  String representing the CSV for the report 
 */

OpenAjax.a11y.FilteredRuleResultsGroup.prototype.toCSV = function(title, header_flag) {

  var eval_title = this.evaluation_result.title;
  var eval_url   = this.evaluation_result.url;
  var date       = this.evaluation_result.date.split(':');
  var eval_time  = date[1] + ":" + date[2];
  var eval_date  = date[0];
  
  if (eval_title.length > 30) eval_title = eval_title.slice(0,27) + "...";
    
//  OpenAjax.a11y.logger.debug("Title: " + title + " Header Flag: " + header_flag + " typeof: " + ((typeof header_flag !== 'boolean') || header_flag));  

  var csv = "";
  
  if ((typeof header_flag !== 'boolean') || header_flag) {
    csv += title + "\n\n";
    csv += "\"OAA ID\"";  
    csv += ",\"Element Description\""; 
    csv += ",\"Element id attribute\""; 
    csv += ",\"Element class attribute\""; 
    csv += ",\"Parent Landmark\""; 
    csv += ",\"Rule ID\"";
    csv += ",\"Type\"";
    csv += ",\"WCAG 2.0 Success Criterion\"";
    csv += ",\"WCAG 2.0 Level\"";
    csv += ",\"Severity\"";
    csv += ",\"Evaluation Result Message\"";
    csv += ",\"Evaluation Date\"";
    csv += ",\"Evaluation Time\"";
    csv += "\"URL Evaluated\"";
    csv += ",\"Title of URL Evaluated\"";
    csv += "\n";
  }  

  var results     = this.filtered_rule_results;
  var results_len = results.length;
  var comma_len   = results_len - 1;

  for (var i = 0; i < results_len; i++) {
                  
     var position = i+1;
            
     var result_item      = results[i];
     var node_results     = result_item.filtered_node_results;
     var node_results_len = node_results.length;
            
     for (var j = 0; j < node_results_len; j++) {
            
       var node_result = node_results[j];
       
       var dom_element = node_result.getDOMElement();

       var cache_item = node_result.getCacheItem();

       csv += "\"" + cache_item.cache_id; 
       csv += "\",\"" + OpenAjax.a11y.util.escapeForJSON(cache_item.toString()); 
       csv += "\",\"" + dom_element.getId(); 
       csv += "\",\"" + dom_element.getClassName(); 
       csv += "\",\"" + dom_element.getParentLandmark(); 
       csv += "\",\"" + node_result.getRuleIdNLS() + ": " + node_result.getRuleSummary();
       csv += "\",\"" + node_result.getRuleRequiredYesNo();
       csv += "\",\"" + node_result.getPrimarySuccessCriterion();
       csv += "\",\"" + node_result.getWCAG20Level();
       csv += "\",\"" + node_result.getResultValue();
       csv += "\",\"" + OpenAjax.a11y.util.escapeForJSON(node_result.getResultMessage());
       csv += "\",\"" + eval_date;
       csv += "\",\"" + eval_time;
       csv += "\",\"" + OpenAjax.a11y.util.escapeForJSON(eval_title);
       csv += "\",\"" + eval_url;
       csv += "\"\n";
               
    }
  }   

  return csv;
};



/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                           FilteredRuleResult                        */
/* ---------------------------------------------------------------- */

/**
 * @constructor FilteredRuleResult
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Represents one of the rule result in a group of rule results.
 *       The count properties and filtered node results list represent the 
 *       counts and node results that remain after the filter is applied
 *       to the node results for the rule results.
 *          
 * @param  {RuleResult}  rule_result  - Rule result object for rule in a group
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property {RuleResult}  rule_result           - Rule result object 
 *
 * @property {Array}       filtered_node_results - List of node result objects 
 *                                                 that met filtering conditions
 * @property {Number}  node_results_filtered_out - Number of node results that 
 *                                                 have been filtered from the list
 *
 * @property  {ResultSummary}  result_summary  - Summary of the node results for 
 *                                               the filtered rule results  
 *
 * @property  {Array}   messages  - Array of strings with rule result messages
 */

 OpenAjax.a11y.FilteredRuleResult = function(rule_result) {

  this.rule_result = rule_result;
  
  this.cache_id = rule_result.cache_id;
  
  this.filtered_node_results = [];

  this.node_results_filtered_out = 0;
 
  this.result_summary = new OpenAjax.a11y.ResultSummary();  
  
  this.messages = [];
  
};

/**
 * @method getFilteredNodeResults
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Populate the filtered node results array with the nodes defined by the filter  
 *          
 * @param {Number} filter  -  Number representing the types of results to include in the array
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getFilteredNodeResults = function(filter) {

  function addNodeResultsPage(node_results) {  
  
    var node_results_len = node_results.length;
    var count = 0;
    
    for (var i = 0; i < node_results_len; i++) {
      var node_result = node_results[i];
      if (node_result.isScopePage()) {
        var filtered_node_result = new OpenAjax.a11y.FilteredNodeResult(node_result);
        filtered_node_results.push(filtered_node_result);
        count++;
      }  
    }
    
    return count;
  }

  function addNodeResultsElement(node_results) {  
  
    var node_results_len = node_results.length;
    var count = 0;
    
    for (var i = 0; i < node_results_len; i++) {
      var node_result = node_results[i];
      if (node_result.isScopeElement()) {
        var filtered_node_result = new OpenAjax.a11y.FilteredNodeResult(node_result);
        filtered_node_results.push(filtered_node_result);
        count++;
      }  
    }
    
    return count;
  }

  function addNodeResults(node_results) {  
  
    var node_results_len = node_results.length;
    
    for (var i = 0; i < node_results_len; i++) {
      var filtered_node_result = new OpenAjax.a11y.FilteredNodeResult(node_results[i]);
      filtered_node_results.push(filtered_node_result);
    }
    return node_results_len;
  }

  var RESULT_FILTER = OpenAjax.a11y.RESULT_FILTER;
  
  var filtered_node_results = this.filtered_node_results;
  var rsf = this.result_summary;
  var rr = this.rule_result;

//  OpenAjax.a11y.logger.debug("  Rule Result " + this.rule_result.getRuleIdNLS() + " Filter=" + filter + " page=" + RESULT_FILTER.PAGE + " " + (RESULT_FILTER.PAGE & filter));  

  if (RESULT_FILTER.VIOLATION            & filter) rsf.addViolations(addNodeResults(rr.node_results_violations));
  if (RESULT_FILTER.PAGE_MANUAL_CHECK    & filter) rsf.addManualChecks(addNodeResultsPage(rr.node_results_manual_checks));
  if (RESULT_FILTER.ELEMENT_MANUAL_CHECK & filter) rsf.addManualChecks(addNodeResultsElement(rr.node_results_manual_checks));
  if (RESULT_FILTER.WARNING              & filter) rsf.addWarnings(addNodeResults(rr.node_results_warnings));
  if (RESULT_FILTER.PASS                 & filter) rsf.addPassed(addNodeResults(rr.node_results_passed));
  if (RESULT_FILTER.PAGE                 & filter) { 
//    OpenAjax.a11y.logger.debug("  Total Page Results: " + rr.node_results_page.length );  
    rsf.addPage(addNodeResults(rr.node_results_page));
  }  
  if (RESULT_FILTER.HIDDEN               & filter) rsf.addHidden(addNodeResults(rr.node_results_hidden));     
  
  var rs = this.rule_result.getResultSummary();
  
  this.node_results_filtered_out = rs.total + rs.manual_checks - rsf.total - rsf.manual_checks;


  // Set filtered node result order property
  
  function byOrder(a,b) {
    if (a.order < b.order) return -1;
    if (a.order > b.order) return 1;
    return 0;
  }
  
  filtered_node_results.sort(byOrder);

  for (var i = 0; i < filtered_node_results.length; i++) {
    filtered_node_results[i].order = (i+1);
  }
  
};

 /**
 * @method hasResults
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Tests if the rule applied to the content in the page 
 *
 * @return {Boolean} True if the application of the rule has results
 */
 
OpenAjax.a11y.FilteredRuleResult.prototype.hasResults = function () {

   return this.result_summary.hasResults();

};

 /**
 * @method getResultSummary
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Gets numerical summary information about the rule results 
 *
 * @return {ResultSummary} Returns the ResultSummary object 
 */
OpenAjax.a11y.FilteredRuleResult.prototype.getResultSummary = function () {

  return this.result_summary;

};

/**
 * @method getResultMessages
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns an NLS string representing the evaluation result message for the rule  
 *
 * @return {Array} An array of strings with rule result messages (typically only one string)
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getResultMessages = function () {

  if ((typeof this.messages !== 'object') || 
      (this.messages.length === 0)) {
    
    this.messages = this.rule_result.getResultMessages(this.result_summary);
  }  
  
  return this.messages;
  
};

/**
 * @method getResultMessage
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns an NLS string representing the evaluation result message for the rule  
 *
 * @return {String} Returns a NLS string 
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getResultMessage = function () {
  
  return this.rule_result.getResultMessage(this.result_summary);
  
};

/**
 * @method getNodeResults
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns an array of node results in severity order 
 *
 * @return {Array} Returns a array of NodeResult objects
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getNodeResults = function () {
 
  return this.filtered_node_results;
  
};

/**
 * @method isRuleEnabled
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Tests whether the rule is enabled or disabled for evaluation  
 *
 * @param {Boolean}  True if rule is enabled, false if rule disabled
 */

OpenAjax.a11y.FilteredRuleResult.prototype.isRuleEnabled = function () {

  return this.rule_result.isRuleEnabled();

};

/**
 * @method isRuleRequired
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Tests whether the rule is mapped as a required or recommended rule  
 *
 * @param {Boolean}  True if rule is a required rule, false if a recommended rule
 */

OpenAjax.a11y.FilteredRuleResult.prototype.isRuleRequired = function () {

  return this.rule_result.isRuleRequired();

};

/**
 * @method getRuleRequiredYesNo
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns 'Yes' or "No' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Yes" if required, otherwise "No" 
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleRequiredYesNo = function () {

  return this.rule_result.getRuleRequiredYesNo();
  
};

/**
 * @method getRuleRequiredOrRecommended
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns 'Required' or "Recommended' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Required" if required, otherwise "Recommended" 
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleRequiredOrRecommended = function () {

  return this.rule_result.getRuleRequiredOrRecommended();
  
};


/**
 * @method getRuleId
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns the id of the rule associated with this rule result
 * 
 * @return {String} String representing the rule id
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleId = function () {

  return this.rule_result.getRuleId();
   
};

/**
 * @method getRuleIdNLS
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns the nls id of the rule associated with this rule result
 * 
 * @return {String} String representing the nls rule id
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleIdNLS = function () {

  return this.rule_result.getRuleIdNLS();
   
};

/**
 * @method getRuleCategory
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get a localized title, url and description of the rule category
 * 
 * @return {Object}  Returns an object with the following propertues:<br/>
 *                   'title':  String representing the Title of the rule category <br/>
 *                   'desc':   String providing a longer description of the rule category<br/>
 *                   'url':    URL to more information about the rule category (maybe blank)<br/>
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleCategory = function () {

  return this.rule_result.getRuleCategory();
  
};

/**
 * @method getRuleCategoryConstant
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns the numeric value for the rule category
 * 
 * @return {Number}  Numeric value of the rule category
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleCategoryConstant = function () {

  return this.rule_result.getRuleCategoryConstant();
  
};


/**
 * @method getRuleScope
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get a localized string of the rule scope (i.e. 'element' or 'page')
 *
 * @return {String} Localized string of the rule scope
 */
 
OpenAjax.a11y.FilteredRuleResult.prototype.getRuleScope = function () {

  return this.rule_result.getRuleScope();
   
};

/**
 * @method isScopePage
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of page, otherwise false
 */
 
OpenAjax.a11y.FilteredRuleResult.prototype.isScopePage = function () {

  return this.rule_result.isScopePage();
  
};

/**
 * @method isScopeElement
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of element, otherwise false
 */
 
OpenAjax.a11y.FilteredRuleResult.prototype.isScopeElement = function () {

  return this.rule_result.isScopeElement();
  
};



/**
 * @method getRuleDefinition
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Gets the definition of the rule
 *
 * @return {String} Localized string of the rule definition based on being 
 *                  required or recommended
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleDefinition = function () {

  return this.rule_result.getRuleDefinition();
  
};




/**
 * @method getRuleSummary
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Gets the summary of the rule
 *
 * @return {String} Localized string of the rule summary based on being 
 *                  required or recommended
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRuleSummary = function () {

  return this.rule_result.getRuleSummary();
  
};

/**
 * @method getPurpose
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Gets an array strings representing the purpose, basically
 *       how does the rule help people with disabilities
 *
 * @return  {Array}  Returns an array of localized string describing the purpose
 */
OpenAjax.a11y.FilteredRuleResult.prototype.getPurpose = function () {

  return this.rule_result.getPurpose();
  
};

/**
 * @method getTargetResourcesDescription
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get a description of the markup or page feature the rule is evaluates
 *
 * @return  {String}  Localized string representing the markup or page feature 
 *                    tested by the rule
 */
OpenAjax.a11y.FilteredRuleResult.prototype.getTargetResourcesDescription = function () {

  return this.rule_result.getTargetResourcesDescription();

};

/**
 * @method getTargetResources
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns an localized array strings representing target resources of 
 *       the rule
 *
 * @return  {Array}  Returns an array of strings identifying the elements and/or
 *                    attributes that the rule evaluates
 */
OpenAjax.a11y.FilteredRuleResult.prototype.getTargetResources = function () {

  return this.rule_result.getTargetResources();

};



/**
 * @method getTechniques
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 * @desc Get the techniques to implement the requirements of the rule 
 *
 * @return  {Array}  Returns an array of localized strings
 */
 
OpenAjax.a11y.FilteredRuleResult.prototype.getTechniques = function () {

  return this.rule_result.getTechniques();
  
};

/**
 * @method getManualCheckProcedures
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Gets manual checking proceedures for evaluating the rule
 *       requirements
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title' : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
OpenAjax.a11y.FilteredRuleResult.prototype.getManualCheckProcedures = function () {

  return this.rule_result.getManualCheckProcedures();

};



/**
 * @method getInformationalLinks
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get information links related to understanding or implementation of the rule
 *
 * @return  {Array}  Returns an array of objects, each object includes the following properties:<br/>
 *                   'type_const' : Number representing the type of information,<br/> 
 *                   'title'      : Title descriping the type of information, <br/>
 *                   'url'        : Link to more information <br/>
 *
 * @example
 *
 * var node_list = [];
 * var info_links = rule.getInformationalLinks();
 * 
 * for(var i = 0; i < info_links.length; i++) {
 *   var info_link = info_links[i];
 *
 *   // Using object properties to create a link element
 *   var node = document.createElement('a');
 *   node.appendChild(document.createTextNode(info_link.title));
 *   node.setAttribute('href',  info_link.url);
 *   node.setAttribute('class', info_link.type_const.toString());
 *
 *   node_list.push(node);
 * }
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getInformationalLinks = function () {

  return this.rule_result.getInformationalLinks();

};

/**
 * @method getPrimarySuccessCriterion
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get information about primary WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Object}  Object representing the success criteria, 
 *                    each object has the following properties:<br/> 
 *                    'id'           : A "P.G.SC" formatted string representing the SC,<br/>  
 *                    'title'        : A localized title for the SC,<br/>
 *                    'description'  : A localized description of the SC,<br/>
 *                    'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                    'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 *
 * @example
 *
 * var sc_info = rule.getPrimarySuccessCriterion();
 * 
 * // Creating a link element to the primary success criterion
 * var node = document.createElement('a');
 * node.appendChild(document.createTextNode(sc_info.id + " " + sc_info.title));
 * node.setAttribute('href',  sc_info.url);
 * node.setAttribute('title', sc_info.description);
 * } 
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getPrimarySuccessCriterion = function () {

  return this.rule_result.getPrimarySuccessCriterion();

};

/**
 * @method getRelatedSuccessCriteria
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get information about the related WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Array}  Array of objects representing the success criteria, each object has the 
 *                   following properties: <br/>
 *                   'id'           : A "P.G.SC" formatted string representing the SC, <br/> 
 *                   'title'        : A localized title for the SC,<br/>
 *                   'description'  : A localized description of the SC,<br/>
 *                   'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                   'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getRelatedSuccessCriteria = function () {

  return this.rule_result.getRelatedSuccessCriteria();

};

/**
 * @method getWCAG20LevelConstant
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get the numerical constant for the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {Number}  Number representing the WCAG 2.0 level 
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getWCAG20LevelConstant = function () {

  return this.rule_result.getWCAG20LevelConstant();

};


/**
 * @method getWCAG20Level
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Get the string representation of the the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {String}  String representing the WCAG 2.0 success criterion level 
 *                    (i.e. A, AA or AAA)
 */

OpenAjax.a11y.FilteredRuleResult.prototype.getWCAG20Level = function () {

  return this.rule_result.getWCAG20Level();

};

/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Creates a text string representation of the filtered rule result object 
 *
 * @return {String} Returns a text string representation of the filtered rule result object
 */

OpenAjax.a11y.FilteredRuleResult.prototype.toString = function () {

 var str = this.getRuleDefinition() + " (";
 
 if (this.has_results) {
   if (this.result_summary.passed || this.result_summary.failures) str += this.result_summary.percent_passed + " percent passed";
   if (this.result_summary.manual_checks) str += " " + this.result_summary.manual_checks + " manual checks";
 }
 else {
   str += "no results";
 }  

 str += ")"; 

 return str;
 
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.FilteredRuleResult
 *
 * @desc Returns a JSON representation of the cache item 
 *          
 * @param {String}  prefix  -  A prefix string typically spaces
 * @param {Boolean} flag    -  if true include node result details
 *
 * @return  {String}  String representing the cache item result object
 */

OpenAjax.a11y.FilteredRuleResult.prototype.toJSON = function(prefix, flag) {

  if (typeof flag !== 'boolean') flag = true;

  var next_prefix = "";
  var next_prefix_2 = "";

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else {
    next_prefix = prefix + "  ";  
    next_prefix_2 = next_prefix + "  ";  
  }  

  var i;
  var json = "";
  var rs = this.result_summary;
  
  var escapeForJSON = OpenAjax.a11y.util.escapeForJSON;
  
  json += prefix + "{ \"rule_id\"         : \"" + this.rule_result.getRuleId() + "\",";
  json += prefix + "  \"definition\"      : \"" + escapeForJSON(this.rule_result.getRuleDefinition()) + "\",";
  json += prefix + "  \"summary\"         : \"" + escapeForJSON(this.rule_result.getRuleSummary())    + "\",";
  json += prefix + "  \"nls_rule_id\"     : \"" + this.rule_result.getRuleIdNLS()         + "\",";
  json += prefix + "  \"required\"        : "   + this.rule_result.isRuleRequired() + ",";
  json += prefix + "  \"wcag_level\"      : \"" + this.rule_result.getWCAG20Level()       + "\",";
  json += prefix + "  \"wcag_primary_id\" : \"" + escapeForJSON(this.rule_result.getPrimarySuccessCriterion()) + "\",";

  json += prefix + "  \"has_results\"   : "  + this.hasResults()  + ",";

  json += prefix + "  \"percent\"       : "  + rs.percent_passed + ",";
  json += prefix + "  \"passed\"        : "  + rs.passed         + ",";
  json += prefix + "  \"violations\"    : "  + rs.violations     + ",";
  json += prefix + "  \"warnings\"      : "  + rs.warnings       + ",";
  json += prefix + "  \"failures\"      : "  + rs.failures       + ",";
  json += prefix + "  \"total\"         : "  + rs.total          + ",";
  json += prefix + "  \"manual\"        : "  + rs.manual_checks  + ",";
  json += prefix + "  \"hidden\"        : "  + rs.hidden         + ",";
  
  
  if (flag) {
    json += prefix + "  \"filtered\"      : "  + this.node_results_filtered_out  + ",";

    var node_results     = this.getNodeResults();
    var node_results_len = node_results.length;
    var comma_count      = node_results_len - 1;
  
    json += prefix + "  \"node_results\" : [";
    for (i = 0; i < node_results_len; i++) {
      json += this.filtered_node_results[i].toJSON(next_prefix);
      if (i < comma_count) json += ",";
    }  
    json += prefix + "  ]";
  }  
  
  json += prefix + "}";

  return json;

};


/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                     FilteredNodeResult                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor FilteredNodeResult
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Constructor for an object that contains a the results of 
 *       the evaluation of a rule on a dom node and is part of a ordered list
 *
 * @param  {NodeResult} node_result             - reference to the rule result object
 */ 

/**
 * @private
 * @constructor Internal Properties
 *
 * @property  {NodeResult} node_result         - reference to the node result object
 * @property  {Number}     order               - Number representing the ordinal 
 *                                               position of the node in the DOM
 */

OpenAjax.a11y.FilteredNodeResult = function (node_result) {

  this.node_result = node_result;
 
  this.order  = node_result.getDOMElement().document_order;
  
};

 /**
 * @method getResultMessage
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns an localized node result message 
 *
 * @return {String} String with node result message
 */
OpenAjax.a11y.FilteredNodeResult.prototype.getResultMessage = function () {

  return this.node_result.getResultMessage();
  
};


/**
 * @method getResultProperties
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get the DOM cache values for the properties associated with a rule
 * 
 * @return {Array} Array of objects with the following properties:<br/>
 *                 'label'       : String label of the property<br/>
 *                 'value'       : String value of the property<br/>
 *                 'description' : String providing additional information about the property <br/>
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getResultProperties = function () {

  return this.node_result.getResultProperties();

};



/**
 * @method getResultValueConstant
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Gets the numerical value of the severity of the result 
 *
 * @return {Number} Returns a number representing the severity of the result
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getResultValueConstant = function () {

  return this.node_result.getResultValueConstant();
 
};

/**
 * @method getResultValue
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Gets severity label, abbreviation, description and style 
 *
 * @return {Object} Returns a object with the following properties: <br/>
 *                  'label'       : String representing the severity <br/>
 *                  'abbrev'      : Abbreviation string of the label<br/>
 *                  'description' : String describing the severity<br/>
 *                  'style'       : String that can used for styling the label<br/> 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getResultValue = function () {

  return this.node_result.getResultValue();
 
};

/**
 * @method getOrdinalPosition
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns a the ordinal position of the element in a list of node results
 * 
 * @return {Number} Returns a number indicating the position in a list of node results
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getOrdinalPosition = function () {

  return this.order;

};

/**
 * @method isRuleEnabled
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Tests whether the rule is enabled or disabled for evaluation  
 *
 * @param {Boolean}  True if rule is enabled, false if rule disabled
 */

OpenAjax.a11y.FilteredNodeResult.prototype.isRuleEnabled = function () {

  return this.node_result.isRuleEnabled();

};

/**
 * @method isRuleRequired
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Tests whether the rule is mapped as a required or recommended rule  
 *
 * @param {Boolean}  True if rule is a required rule, false if a recommended rule
 */

OpenAjax.a11y.FilteredNodeResult.prototype.isRuleRequired = function () {

  return this.node_result.isRuleRequired();

};


/**
 * @method getRuleRequiredYesNo
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns 'Yes' or "No' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Yes" if required, otherwise "No" 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleRequiredYesNo = function () {

  return this.node_result.getRuleRequiredYesNo();
  
};

/**
 * @method getRuleRequiredOrRecommended
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns 'Required' or "Recommended' depending on whether the rule is required or recommended rule  
 *
 * @param {String} Returns "Required" if required, otherwise "Recommended" 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleRequiredOrRecommended = function () {

  return this.node_result.getRuleRequiredOrRecommended();
  
};



/**
 * @method getRuleId
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get the programmatic id that uniquely identifies the rule
 *
 * @return {String} The rule id
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleId = function () {

  return this.node_result.getRuleId();
   
};

/**
 * @method getRuleIdNLS
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get a localized human readable id for uniquely identifying the rule
 *
 * @return {String} Localized string of the rule id
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleIdNLS = function () {

  return this.node_result.getRuleIdNLS();
   
};



/**
 * @method getRuleCategory
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get a localized title, url and description of the rule category
 * 
 * @return {Object}  Returns an object with the following propertues:<br/>
 *                   'title':  String representing the Title of the rule category <br/>
 *                   'desc':   String providing a longer description of the rule category<br/>
 *                   'url':    URL to more information about the rule category (maybe blank)<br/>
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleCategory = function () {

  return this.node_result.getRuleCategory();
  
};


/**
 * @method getRuleCategoryConstant
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns the numeric value for the rule category
 * 
 * @return {Number}  Numeric value of the rule category
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleCategoryConstant = function () {

  return this.node_result.getRuleCategoryConstant();
  
};

/**
 * @method getRuleScope
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get a localized string of the rule scope (i.e. 'element' or 'page')
 *
 * @return {String} Localized string of the rule scope
 */
 
OpenAjax.a11y.FilteredNodeResult.prototype.getRuleScope = function () {

  return this.node_result.getRuleScope(); 
  
};

/**
 * @method isScopePage
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of page, otherwise false
 */
 
OpenAjax.a11y.FilteredNodeResult.prototype.isScopePage = function () {

  return this.node_result.isScopePage(); 
  
};

/**
 * @method isScopeElement
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns an localized string of the rule scope (i.e. element or page)
 *
 * @return {Boolean} True if the rule has a scope of element, otherwise false
 */
 
OpenAjax.a11y.FilteredNodeResult.prototype.isScopeElement = function () {

  return this.node_result.isScopeElement(); 
  
};

/**
 * @method getRuleDefinition
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Gets the definition of the rule
 *
 * @return {String} Localized string of the rule definition based on being 
 *                  required or recommended
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleDefinition = function () {

  return this.node_result.getRuleDefinition();
  
};

/**
 * @method getRuleSummary
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc  Gets the summary of the rule
 *
 * @return {String} Localized string of the rule summary based on being 
 *                  required or recommended
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRuleSummary = function () {

  return this.node_result.getRuleSummary();
  
};

/**
 * @method getPurpose
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Gets an array strings representing the purpose, basically
 *       how does the rule help people with disabilities
 *
 * @return  {Array}  Returns an array of localized string describing the purpose
 */
OpenAjax.a11y.FilteredNodeResult.prototype.getPurpose = function () {

  return this.node_result.getPurpose();

};


/**
 * @method getTargetResourcesDescription
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get a description of the markup or page feature the rule is evaluates
 *
 * @return  {String}  Localized string representing the markup or page feature 
 *                    tested by the rule
 */
OpenAjax.a11y.FilteredNodeResult.prototype.getTargetResourcesDescription = function () {

  return this.node_result.getTargetResourcesDescription();

};

/**
 * @method getTargetResources
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns an localized array strings representing target resources of 
 *       the rule
 *
 * @return  {Array}  Returns an array of strings identifying the elements and/or
 *                    attributes that the rule evaluates
 */
OpenAjax.a11y.FilteredNodeResult.prototype.getTargetResources = function () {

  return this.node_result.getTargetResources();

};


/**
 * @method getTechniques
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get the techniques to implement the requirements of the rule 
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title' : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
 
OpenAjax.a11y.FilteredNodeResult.prototype.getTechniques = function () {

  return this.node_result.getTechniques();
  
};

/**
 * @method getManualCheckProcedures
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Gets manual checking proceedures for evaluating the rule
 *       requirements
 *
 * @return  {Array}  Returns an array of objects with localized strings and urls.<br/>
 *                   Each object has the following properties:<br/>
 *                   'title' : Localized string describing the technique<br/> 
 *                   'url': URL to more information about the technique<br/>
 */
 
OpenAjax.a11y.FilteredNodeResult.prototype.getManualCheckProcedures = function () {

  return this.node_result.getManualCheckProcedures();

};

/**
 * @method getInformationalLinks
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get information links related to understanding or implementation of the rule
 *
 * @return  {Array}  Returns an array of objects, each object includes the following properties:<br/>
 *                   'type_const' : Number representing the type of information,<br/> 
 *                   'title'      : Title descriping the type of information, <br/>
 *                   'url'        : Link to more information <br/>
 *
 * @example
 *
 * var node_list = [];
 * var info_links = rule.getInformationalLinks();
 * 
 * for(var i = 0; i < info_links.length; i++) {
 *   var info_link = info_links[i];
 *
 *   // Using object properties to create a link element
 *   var node = document.createElement('a');
 *   node.appendChild(document.createTextNode(info_link.title));
 *   node.setAttribute('href',  info_link.url);
 *   node.setAttribute('class', info_link.type_const.toString());
 *
 *   node_list.push(node);
 * }
 */
OpenAjax.a11y.FilteredNodeResult.prototype.getInformationalLinks = function () {

  return this.node_result.getInformationalLinks();

};



/**
 * @method getPrimarySuccessCriterion
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get information about primary WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Object}  Object representing the success criteria, 
 *                    each object has the following properties:<br/> 
 *                    'id'           : A "P.G.SC" formatted string representing the SC,<br/>  
 *                    'title'        : A localized title for the SC,<br/>
 *                    'description'  : A localized description of the SC,<br/>
 *                    'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                    'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 * @example
 *
 * var sc_info = rule.getPrimarySuccessCriterion();
 * 
 * // Creating a link element to the primary success criterion
 * var node = document.createElement('a');
 * node.appendChild(document.createTextNode(sc_info.id + " " + sc_info.title));
 * node.setAttribute('href',  sc_info.url);
 * node.setAttribute('title', sc_info.description);
 * } 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getPrimarySuccessCriterion = function () {

  return this.node_result.getPrimarySuccessCriterion();

};

/**
 * @method getRelatedSuccessCriteria
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get information about the related WCAG 2.0 Success Criteria for the rule
 *
 * @return  {Array}  Array of objects representing the success criteria, each object has the 
 *                   following properties: <br/>
 *                   'id'           : A "P.G.SC" formatted string representing the SC, <br/> 
 *                   'title'        : A localized title for the SC,<br/>
 *                   'description'  : A localized description of the SC,<br/>
 *                   'url'          : A url to the SC in the WCAG 2.0 document<br/>
 *                   'level'        : The level of the Success Criterion (e.g. A, AA or AAA)<br/>
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getRelatedSuccessCriteria = function () {

  return this.node_result.getRelatedSuccessCriteria();

};

/**
 * @method getWCAG20LevelConstant
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get the numerical constant for the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {Number}  Number representing the WCAG 2.0 level 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getWCAG20LevelConstant = function () {

  return this.node_result.getWCAG20LevelConstant();

};

/**
 * @method getWCAG20Level
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Get the string representation of the the WCAG 2.0 Success Criterion Level 
 *       based on the primary id of the rule
 *
 * @return  {String}  String representing the WCAG 2.0 success criterion level 
 *                    (i.e. A, AA or AAA)
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getWCAG20Level = function () {

  return this.node_result.getWCAG20Level();

};

/**
 * @method getXPath
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns the xpath of the associated element
 * 
 * @return {String} information about the node result 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getXPath = function () {
  
  return this.node_result.getXPath();
 
};

/**
 * @method getDOMElement
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Returns the dom element object
 *
 * @return {String} Returns a dom element associated with the cache item
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getDOMElement = function () {

  return this.node_result.getDOMElement();
  
};



/**
 * @method getCacheItem
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Gets the cache item associated with the node result
 *
 * @return {Object} Returns a cache item object
 */

OpenAjax.a11y.FilteredNodeResult.prototype.getCacheItem = function () {

  return this.node_result.getCacheItem();
 
};


/**
 * @method toString
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Creates a text string representation of the node result object 
 *
 * @return {String} Returns a text string representation of the node result object
 */

OpenAjax.a11y.FilteredNodeResult.prototype.toString = function () {

  return this.node_result.toString();
  
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.FilteredNodeResult
 *
 * @desc Creates JSON object descibing the properties of the node result
 *
 * @param {String} prefix  -  A prefix string typically spaces
 * 
 * @return String information about the node result 
 */

OpenAjax.a11y.FilteredNodeResult.prototype.toJSON = function (prefix) {

  return this.node_result.toJSON();
  
};

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* -------------------------------------------------------------------- */
/*     Formats Filtered Rule Results Groups objects                     */
/*     for Tree View Rendering                                          */
/* -------------------------------------------------------------------- */

/** 
 * @namespace OpenAjax.a11y.formatters
 */

OpenAjax.a11y.formatters = OpenAjax.a11y.formatters || {};

/**
 * @constructor TreeViewOfFilteredRuleResultsGroups
 *
 * @memberOf OpenAjax.a11y.formatters
 *
 * @desc Constructs a list of rule groups results that can be used by tree views 
 *       (i.e. XUL custom tree views)
 *
 * @param  {FilteredRuleResultsGroups}  filtered_rule_results_groups  - Filtered rule
 *                                                                      results groups 
 *                                                                      object 
 *
 * @param  {Number}  level - level of this list in the tree structure 
 *
 * 
 * @property  {FilteredRuleResultsGroups}  filtered_rule_results_groups  - Filtered rule
 *                                                                         results groups 
 *                                                                         object reference
 *
 * @property  {Array}  rule_result_items  - List of rule result itmes with properties 
 *                                          optimized for tree view or list rendering   
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups = function(filtered_rule_results_groups, level) {

  var cache_nls = OpenAjax.a11y.cache_nls.getCacheNLS();
  var boolean_values = cache_nls.boolean_values;

  if (typeof level !== 'number') level = 0;
  
  // Initialize object properties
  this.filtered_rule_results_groups = filtered_rule_results_groups;  
  this.rule_result_items = [];
  
  var frr_groups     = filtered_rule_results_groups.filtered_rule_results_groups;
  var frr_groups_len = frr_groups.length;

  var rrg_item = null;

  for (var i = 0; i < frr_groups_len; i++) {
  
    var frrg =  frr_groups[i];
    
    var rs = frrg.getResultSummary();
    var has_results = rs.hasResults();
    
    var failures_prop = "zero";

    if (rs.warnings   > 0)  failures_prop  = "warning";
    if (rs.violations > 0)  failures_prop  = "violation";
    
    var number_of_rules = frrg.getNumberOfRules();
    
    var summary = frrg.title + " (";    
    if (number_of_rules === 1) summary += "1 " + cache_nls.rule + ")";
    else summary += number_of_rules + " " + cache_nls.rules + ")";

    // Add information about number of rules
    var tv_frrg;
    if (typeof frrg.filtered_rule_results_groups === 'undefined') {
      tv_frrg = new OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroup(frrg, (level+1));   
    } 
    else {
      tv_frrg = new OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups(frrg, (level+1)); 
    }

    var children = tv_frrg.getOpenRuleResults();
    
    var has_rules   = frrg.hasRules();

    rrg_item = {
      // properties for tree view
      container : true,
      open      : true,
      level     : level,
      type      : 'FILTERED_RULE_GROUP_RESULT',
      
      // Filtered rule result properties
      
      filtered_rule_results_group: frrg,
      
      tree_view    : tv_frrg,
      
      children     : children,

      group_id     : frrg.group_id,

      summary      : summary,
      summary_prop : "group" + level,
      
      definition   : summary,
      message      : "",

      wcag20_level       : 0,
      wcag20_level_label : "",
      wcag20_level_prop  : "",

      required       : null,      
      required_label : "",
      required_prop  : "",

      has_results          : has_results,
      
      percent_passed       : rs.percent_passed,
      percent_passed_label : has_results ? rs.percent_passed.toString() : '-',
      percent_passed_prop  : (rs.percent_passed < 1) ? 'zero' : 'passed',

      passed_count         : rs.passed,
      passed_label         : has_results ? rs.passed.toString() : '-',
      passed_prop          : (rs.passed >= 1) ? 'passed' : 'zero',

      violations_count     : rs.violations,
      violations_label     : has_results ? rs.violations.toString() : '-',
      violations_prop      : (rs.violations >= 1) ? 'violation' : 'zero',
      
      warnings_count       : rs.warnings,
      warnings_label       : has_results ? rs.warnings.toString() : '-',
      warnings_prop        : (rs.warnings >= 1) ? 'warning' : 'zero',
      
      failures_count       : rs.failures,
      failures_label       : has_results ? rs.failures.toString() : '-',
      failures_prop        : (rs.failures >= 1) ? failures_prop : 'zero',
            
      manual_checks_count  : rs.manual_checks,
      manual_checks_label  : has_results ? rs.manual_checks.toString() : '-',
      manual_checks_prop   : (rs.manual_checks >= 1) ? 'manual_check' : 'zero',      
      
      hidden_count         : rs.hidden,
      hidden_label         : has_results ? rs.hidden.toString() : '-',
      hidden_prop          : (rs.hidden >= 1) ? 'hidden' : 'zero'
    
    };

    this.rule_result_items.push(rrg_item);
   
  }

};


/**
 * @method setOpenState
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups
 *
 * @desc Sets open state on all container items in the list
 *       Used for expanding or collapsing all leafs in a tree
 *
 * @param  {Boolean} flag  -  If true all container elements will be set 
 *                            to open, otherwise containers element set 
 *                            to close  
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups.prototype.setOpenState = function (flag) {

   function setContainers(list) {
   
     for (var i = 0; i < list.length; i++) {
   
        var item = list[i];
        
        if (item.container) {
          item.open = flag;
          
          if (item.children && item.children.length) setContainers(item.children);
        }     
     }
   }
   
   if (typeof flag !== 'boolean') flag = true;
   
   setContainers(this.rule_result_items);

};

/**
 * @method getOpenRuleResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups
 *
 * @desc Returns an array of all the rule result items, including the 
 *       items in the children property of items with open property set to true
 *
 * @return  {Array} Array of rule result items optimized for a tree view
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups.prototype.getOpenRuleResults = function() {

  function getOpenItems(items) {
  
    if (typeof items !== 'object') return;
  
    var items_len = items.length;

    for (var i = 0; i < items_len; i++) {
    
      var item = items[i];
    
      list.push(item);
      
      if (item.open && item.children && item.children.length) { 
         getOpenItems(item.children);
      }
    }      
  }
      
  var list = [];

  var rr_items = this.rule_result_items;
  
  getOpenItems(rr_items);
  
  return list;
  
};

/**
 * @method sortedListOfRules
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups
 *
 * @desc Sorts the list of rule result items based on item property values
 *
 * @param  {String}  sort_property - Name of property to sort, property names include: 'violations_count', 'warnings_count', 
 *                                   'failures_count', 'manual_checks_count', 'passed_count'
 *                                   
 * @param  {Number}  order         - If 1 sort ascending, else sort descending 
 *
 * @return  {Array} Array of objects optimized for display as a tree
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroups.prototype.sortListOfRuleResults = function(sort_property, order) {

  function sortProperty(a, b) {
  
//    OpenAjax.a11y.logger.debug(" Group IDs: a=" + a.group_id  + " b=" + b.group_id);

    if (sort_property !== 'summary') {
      if (!a.has_results) return 1;
      if (!b.has_results) return -1;
    }  

    if (a[sort_property] > b[sort_property]) return  1 * order;
    if (a[sort_property] < b[sort_property]) return -1 * order;
    
    //tie breaker: name ascending is the second level sort
    
    if (sort_property != "violations_count") {
      if (a.violations_count > b.violations_count) return 1;
      if (a.violations_count < b.violations_count) return -1;
    }

    if (sort_property != "manual_checks_count") {
      if (a.manual_checks_count > b.manual_checks_count) return 1;
      if (a.manual_checks_count < b.manual_checks_count) return -1;
    }

    if (sort_property != "passed_count") {
      if (a.passed_count > b.passed_count) return 1;
      if (a.passed_count < b.passed_count) return -1;
    }
    
    if (sort_property != "hidden_count") {
      if (a.hidden_count > b.hidden_count) return 1;
      if (a.hidden_count < b.hidden_count) return -1;
    }
    
    return 0;  
  }
  
//  OpenAjax.a11y.logger.debug("  SORT ITEM: " + this.title + " SORT BY: " + sort_property + " ORDER: " + order);  
    
  for (var i = 0; i < this.rule_result_items.length; i++) {
  
    var rr_item = this.rule_result_items[i];
  
    var tv = rr_item.tree_view;

    if (tv) { 
      rr_item.children = tv.sortListOfRuleResults(sort_property, order);
    }
  
  }

  if (typeof sort_property !== 'string') sort_property = 'summary';
  if (typeof order !== 'number') order = 1;

  this.rule_result_items = this.rule_result_items.sort(sortProperty);

  return this.rule_result_items;

};


/* ---------------------------------------------------------------- */
/*        Tree View of Filtered Rule Results Group                  */
/* ---------------------------------------------------------------- */

/**
 * @constructor treeViewOfFilteredRuleResultsGroup
 *
 * @memberOf OpenAjax.a11y.formatters
 *
 * @desc Constructs a list of rule group object results that can be used by tree views 
 *       (i.e. XUL custom tree views)
 *
 * @param  {FilteredRuleResultsGroup}  filtered_rule_results_group  - Filtered rule 
 *                                                                    results group 
 *                                                                    object
 *
 * @param  {Number}  level  - level of this list in a tree structure 
 * 
 * @property {FilteredRuleResultsGroup}  filtered_rule_results_group - Filtered rule
 *                                                                     results group
 *                                                                     object reference 
 *
 * @property {Array}  rule_result_items  - List of rule result itmes with properties 
 *                                         optimized for tree view or list rendering   
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroup = function(filtered_rule_results_group, level) {

  var cache_nls = OpenAjax.a11y.cache_nls.getCacheNLS();
  var boolean_values = cache_nls.boolean_values;

  if (typeof level !== 'number') level = 0;
  
  // Initialize object properties
  this.filtered_rule_results_group = filtered_rule_results_group;  
  this.rule_result_items = [];

//  OpenAjax.a11y.logger.debug("  create tree view: " + filtered_rule_results_group + " " + filtered_rule_results_group.filtered_rule_results);  

  var filtered_rule_results     = filtered_rule_results_group.filtered_rule_results;  
  var filtered_rule_results_len = filtered_rule_results.length;

  for (var i = 0; i < filtered_rule_results_len; i++) {
  
    var frr =  filtered_rule_results[i];
    var rs  = frr.getResultSummary();
    var has_results = rs.hasResults();

    var failures_prop = "zero";
    
    if (rs.violations > 0)  failures_prop  = "violation";
    if (rs.warnings   > 0)  failures_prop  = "warning";
    
    var rr_item = {
      // properties for tree view
      container : false,
      open      : false,
      level     : level,
      type      : 'FILTERED_RULE_RESULT',
      
      // Filtered rule result properties
      
      filtered_rule_result : frr,
      
      children      : [],

      group_id      : frr.group_id,
      
      has_results   : has_results,
      
      required       : frr.isRuleRequired(),      
      required_label : frr.getRuleRequiredYesNo(),
      required_prop  : frr.getRuleRequiredYesNo().toLowerCase(),
      

      summary       : frr.getRuleSummary(),
      summary_prop  : has_results ? "" : "not_applicable",
      
      definition    : frr.getRuleDefinition(),
      messages      : frr.getResultMessages(),
      message       : frr.getResultMessage(),

      wcag20_level       : frr.getWCAG20LevelConstant(),
      wcag20_level_label : frr.getWCAG20Level(),
      wcag20_level_prop  : frr.getWCAG20Level().toLowerCase(),
      
      percent_passed       : rs.percent_passed,
      percent_passed_label : has_results ? rs.percent_passed.toString() : '-',
      percent_passed_prop  : has_results ? 'passed' : 'zero',

      passed_count         : rs.passed,
      passed_label         : has_results ? rs.passed.toString() : '-',
      passed_prop          : has_results ? 'passed' : 'zero',

      violations_count     : rs.violations,
      violations_label     : has_results ? rs.violations.toString() : '-',
      violations_prop      : (rs.violations >= 1) ? 'violation' : 'zero',
      
      warnings_count       : rs.warnings,
      warnings_label       : has_results ? rs.warnings.toString() : '-',
      warnings_prop        : (rs.warnings >= 1) ? 'warning' : 'zero',
      
      failures_count       : rs.failures,
      failures_label       : has_results ? rs.failures.toString() : '-',
      failures_prop        : (rs.failures >= 1) ? failures_prop : 'zero',
            
      manual_checks_count  : rs.manual_checks,
      manual_checks_label  : has_results ? rs.manual_checks.toString() : '-',
      manual_checks_prop   : (rs.manual_checks >= 1) ? 'manual_check' : 'zero',      
      
      hidden_count         : rs.hidden,
      hidden_label         : has_results ? rs.hidden.toString() : '-',
      hidden_prop          : (rs.hidden >= 1) ? 'hidden' : 'zero'
    
    };

    this.rule_result_items.push(rr_item);
   
//    OpenAjax.a11y.logger.debug("  PERCENT PASS LABEL: " + rrg_item.percent_passed_label);
  }

};

/**
 * @method getOpenRuleResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroup
 *
 * @desc Returns an array of all the rule result items, including the 
 *       items in the children property of items with open property set to true
 *
 * @return  {Array} Array of rule result items optimized for a tree view
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroup.prototype.getOpenRuleResults = function() {

  return this.rule_result_items;
  
};

/**
 * @method sortedListOfRules
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroup
 *
 * @desc Sorts the list of rule result items based on item property values
 *
 * @param  {String}  sort_property - Name of property to sort, property names include: 'violations_count', 'warnings_count', 
 *                                   'failures_count', 'manual_checks_count', 'passed_count'
 *
 * @param  {Number}  order         - If 1 sort ascending, else sort descending 
 *
 * @return  {Array} Array of objects optimized for display as a tree
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResultsGroup.prototype.sortListOfRuleResults = function(sort_property, order) {

  function sortProperty(a, b) {
  
//     OpenAjax.a11y.logger.debug("  COMPARE RULES A:(" + a.summary + ") B:(" + b.summary + ")");  

    if (sort_property !== 'summary' || sort_property !== 'percent_passed') {
      if (!a.has_results) return 1;
      if (!b.has_results) return -1;
    }  

    if (a[sort_property] > b[sort_property]) return  1 * order;
    if (a[sort_property] < b[sort_property]) return -1 * order;
    
    //tie breaker: name ascending is the second level sort
    
    if (sort_property != "wcag20_level") {
      if (a.wcag20_level < b.wcag20_level) return 1;
      if (a.wcag20_level > b.wcag20_level) return -1;
    }
    
    if (sort_property != "required") {
      if (!a.required &&  b.required) return 1;
      if ( a.required && !b.required) return -1;
    }

    if (sort_property != "violations_count") {
      if (a.violations_count < b.violations_count) return 1;
      if (a.violations_count > b.violations_count) return -1;
    }

    if (sort_property != "manual_checks_count") {
      if (a.manual_checks_count < b.manual_checks_count) return 1;
      if (a.manual_checks_count > b.manual_checks_count) return -1;
    }

    if (sort_property != "passed_count") {
      if (a.passed_count > b.passed_count) return 1;
      if (a.passed_count < b.passed_count) return -1;
    }
    
    if (sort_property != "hidden_count") {
      if (a.hidden_count < b.hidden_count) return 1;
      if (a.hidden_count > b.hidden_count) return -1;
    }
    
    return 0;  
  }

  if (typeof sort_property !== 'string') sort_property = 'wcag20_level';
  if (typeof order !== 'number') order = -1;

//  OpenAjax.a11y.logger.debug("  SORT ITEM: " + this.title + " SORT BY: " + sort_property + " ORDER: " + order);  

  this.rule_result_items = this.rule_result_items.sort(sortProperty);

  return this.rule_result_items.sort(sortProperty);

};

/* ---------------------------------------------------------------- */
/*                           FilteredRuleResult                     */
/* ---------------------------------------------------------------- */

/**
 * @constructor TreeViewOfFilteredRuleResult
 *
 * @memberOf OpenAjax.a11y.formatters
 *
 * @desc Creates a list of node results formatted for display in a tree view
 *          
 * @param  {FilteredRuleResult}  filtered_rule_result  - Filtered rule result 
 *                                                       object 
 *
 * @property {FilteredRuleResult}  filtered_rule_result  - Filtered rule result
 *                                                         object reference
 *
 * @property {Array}  node_result_items - List of node result items optimized
 *                                        for tree or list view rendering
 */

 OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult = function(filtered_rule_result) {

  function addFilteredElementCount(nr_items, count) {

    if (count === 0) return;

    var str;

    if (count === 1) str = count + " " + cache_nls.filteredItem;
    else str = count + " "  + cache_nls.filteredItems;

    var row = { level : 0,
                container : false,
                open : false,
                
                node_result : null,
                position : -1,
                
                message : str,
                message_prop : "message"
        };

    nr_items.push(row);
  }

  function addProperty(nr_item, label, value) {
  
    var prop_item = { level     : 1,
                      container : false,
                      open      : false,

                      node_result : nr_item.node_result,
                      position    : nr_item.position,

                      property_name      : label,  
                      property_name_prop : "",
                      value              : "",
                      value_prop         : "" 
                   };
 
    if (value !== null) {
      // if value is a string test to see if it is empty
      if (typeof value == "string") {
        if (value.length) {
          prop_item.value = value;
        } 
        else {
          prop_item.value      = cache_nls.empty;
          prop_item.value_prop = 'empty';
        }
      }
      else {
        prop_item.value = value.toString();
      }  
    }  
    else {
      prop_item.value = cache_nls.undefined;
      prop_item.value_prop = 'empty';
    }
    
    nr_item.properties.push(prop_item);    
        
  }

  var cache_nls = OpenAjax.a11y.cache_nls.getCacheNLS();
  var boolean_values = cache_nls.boolean_values;

  // Initialize properties
  this.filtered_rule_result = filtered_rule_result;  
  this.node_result_items = [];
  
  var node_results     = filtered_rule_result.filtered_node_results;
  var node_results_len = node_results.length;

  for (var i = 0; i < node_results_len; i++) {
  
    var nr = node_results[i];
    
    var properties = nr.getResultProperties();
    
    var nr_item = { level        : 0,
                    container    : false,
                    open         : false,

                    properties   : [],
                    node_result  : nr,
                    
                    severity     : nr.getResultValue(),
                    
                    element      : nr.getCacheItem().toString(), 
                    element_prop : "element",
               
                    order       : nr.getOrdinalPosition(),
                    order_prop  : "order",

                    result       : nr.getResultValue().abbrev,
                    result_prop  : nr.getResultValue().style,

                    result_message      : nr.getResultMessage(), 
                    result_message_prop : "result_message_prop"


                 };

/*
    var message_str = cache_nls.message;    
    // Capitalize
    message_str = message_str.charAt(0).toUpperCase() + message_str.slice(1);
    addProperty(nr_item, message_str, nr.getResultMessage());
        
    if (!nr.cache_item.is_page_element) {
      for (var j = 0; j < properties.length; j++) {
        addProperty(nr_item, properties[j].label, properties[j].value);
      } // end for  
    } 
*/

   this.node_result_items.push(nr_item);
  
  }
  
  addFilteredElementCount(this.node_result_items, filtered_rule_result.node_results_filtered_out);
  
};


/**
 * @method setOpenState
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult
 *
 * @desc Sets open state on all container items in the list
 *       Used for expanding or collapsing all leafs in a tree
 *
 * @param  {Boolean} flag  -  If true all container elements will be set 
 *                            to open, otherwise containers element set 
 *                            to close  
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult.prototype.setOpenState = function (flag) {

   function setContainers(list) {
   
     for (var i = 0; i < list.length; i++) {
   
        var item = list[i];
        
        if (item.container) {
          item.open = flag;
          
          if (item.properties && item.properties.length) setContainers(item.properties);
        }     
     }
   }
   
   if (typeof flag !== 'boolean') flag = true;
   
   setContainers(this.node_result_items);

};


/**
 * @method getOpenNodeResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult
 *
 * @desc Returns an array of all the node result items, including the 
 *       items in the children property of items with open property set to true
 *
 * @return  {Array} Array of node result items optimized for a tree view
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult.prototype.getOpenNodeResults = function() {

  function getOpenItems(items) {
  
    if (typeof items !== 'object') return;
  
    var items_len = items.length;

    for (var i = 0; i < items_len; i++) {
    
      var item = items[i];
    
      list.push(item);
      
      if (item.open && item.properties && item.properties.length) { 
         getOpenItems(item.properties);
      }
    }      
  }
      
  var list = [];
  
  getOpenItems(this.node_result_items);
  
  return list;
  
};

/**
 * @method sortListOfNodeResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult
 *
 * @desc Sorts the list of node result items based on item property values
 *
 * @param  {String}  sort_property - Name of property to sort, property names 
 *                                   include: 'result', 'element', 'position'
 *
 * @param  {Number}  order         - If 1 sort ascending, else sort descending 
 *
 * @return  {Array} Array of objects optimized for display as a tree
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredRuleResult.prototype.sortListOfNodeResults = function(sort_property, order) {

  function sortProperty(a, b) {
  
    if (typeof a.message === 'string') return 1;
    if (typeof b.message === 'string') return -1;
    
    if (a[sort_property] > b[sort_property]) return  -1 * order;
    if (a[sort_property] < b[sort_property]) return 1 * order;
    
    //tie breaker: name ascending is the second level sort

    if (sort_property != "severity") {
      if (a.severity > b.severity) return 1;
      if (a.severity < b.severity) return -1;
    }
    
    if (sort_property != "order") {
      if (a.order > b.order) return 1;
      if (a.order < b.order) return -1;
    }
    
    if (sort_property != "element") {
      if (a.element > b.element) return 1;
      if (a.element < b.element) return -1;
    }

    if (sort_property != "result_message") {
      if (a.result_message > b.result_message) return 1;
      if (a.result_message < b.result_message) return -1;
    }
    

    return 0;  
  }

  return this.node_result_items.sort(sortProperty);

};


/**
 * @constructor TreeViewOfFilteredCacheItemResults
 *
 * @memberOf OpenAjax.a11y.formatters
 *
 * @desc Constructs a list of cache item results that can be used by tree views 
 *       (i.e. XUL custom tree views)
 *
 * @param  {FilteredCacheItemResults}  filtered_cache_item_results  - Filtered cache 
 *                                                                    item result object
 *                                                                    for tree view 
 *                                                                    presentation
 * 
 * @property {FilteredCacheItemResults}   filtered_cache_item_results - Filtered cache
 *                                                                      item result object
 *                                                                      reference
 *
 * @property {Array}  cache_item_results - An array of cache item result items for use
 *                                         be tree or list view rendering
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults = function(filtered_cache_item_results) {

  function addCacheItemResults(cache_item_results, level) {
  
    var cache_item_results_len = cache_item_results.length;
    
    var list = [];

    for (var i = 0; i < cache_item_results_len; i++) {

      var cir =  cache_item_results[i];
      
      var rs = cir.getResultSummary();

//      OpenAjax.a11y.logger.debug("  Cache Item: " + cir.cache_item.toString());

      var failures_prop  = "zero";    
      if (rs.warnings   > 0)  failures_prop  = "warning";
      if (rs.violations > 0)  failures_prop  = "violation";

      var children = [];
      var position = position_count;
      position_count++;
      if (cir.is_tree) children = addCacheItemResults(cir.children, (level+1));
      
      var c_item = {
        // properties for tree view
        container : cir.is_tree,
        open      : cir.is_tree,
        level     : level,
        type      : 'CACHE_ITEM_RESULT',
      
        // Filtered rule result properties
    
        position            : cir.getOrdinalPosition(),
        cache_item_result   : cir,
        node_results        : cir.getNodeResults(),
    
        children     : children,
            
        element      : cir.getCacheItem().toString(),
        element_prop : "",
      
        passed_count : rs.passed,
        passed_label : rs.passed.toString(),
        passed_prop  : (rs.passed > 0) ? 'passed' : 'zero',

        violations_count : rs.violations,
        violations_label : rs.violations.toString(),
        violations_prop  : (rs.violations > 0) ? 'violation' : 'zero',
      
        warnings_count : rs.warnings,
        warnings_label : rs.warnings.toString(),
        warnings_prop  : (rs.warnings > 0) ? 'warning' : 'zero',

        failures_count : rs.failures,
        failures_label : rs.failures.toString(),
        failures_prop  : (rs.failures > 0) ? failures_prop : 'zero', 

        manual_checks_count : rs.manual_checks,
        manual_checks_label : rs.manual_checks.toString(),
        manual_checks_prop  : (rs.manual_checks > 0) ? 'manual_check' :' zero',      
      
        hidden_count : rs.hidden,
        hidden_label : rs.hidden.toString(),
        hidden_prop  : (rs.hidden > 0) ? 'hidden' : 'zero'
    
      };
      
      list.push(c_item);
  
    }
    
    return list;
    
  } // end addCacheItemResults   
  
  var position_count = 1;
  
  this.filtered_cache_item_results = filtered_cache_item_results;

//  OpenAjax.a11y.logger.debug("CACHE ITEM LIST: " + this.element_type + " (" + this.cache_item_results + ")");

  this.cache_item_list = addCacheItemResults(filtered_cache_item_results.cache_item_results, 0);
    
};

/**
 * @method setOpenState
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults
 *
 * @desc Sets open state on all container items in the list
 *       Used for expanding or collapsing all leafs in a tree
 *
 * @param  {Boolean} flag  -  If true all container elements will be set 
 *                            to open, otherwise containers element set 
 *                            to close  
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults.prototype.setOpenState = function (flag) {

   function setContainers(list) {
   
     for (var i = 0; i < list.length; i++) {
   
        var item = list[i];
        
        if (item.container) {
          item.open = flag;
          
          if (item.children && item.children.length) setContainers(item.children);
        }     
     }
   }
   
   if (typeof flag !== 'boolean') flag = true;
   
   setContainers(this.cache_item_list);

};

/**
 * @method getOpenCacheItemResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults
 *
 * @desc Creates an array of cache item result objects with properties optimized for customized tree view display, that can be sorted
 *
 * @param  {Boolean} all_flag  -  If true all elements of the list including children are returned without levels 
 *
 * @return  {Array} Array of objects optimized for display as a XUL custom tree
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults.prototype.getOpenCacheItemResults = function(all_flag) {

  function getOpenItems(items) {
  
    if (typeof items !== 'object') return;
  
    var items_len = items.length;

    for (var i = 0; i < items_len; i++) {
    
      var item = items[i];
    
      if (all_flag) {
        item.container = false;
        item.open      = true;
        item.level     = 0;
        
        list.push(item);      
        
        if (item.children && item.children.length) { 
          getOpenItems(item.children);
        }
      }
      else {
      
        list.push(item);      
        
        if (item.open && item.children && item.children.length) { 
          getOpenItems(item.children);
        }
      }  
    }      
  }
      
  if (typeof all_flag !== 'boolean') all_flag = false;     
      
  var list = [];

  var ci_list = this.cache_item_list;
  
  getOpenItems(ci_list);
  
  this.open_cache_item_list = list;
  
  return list;

};

/**
 * @method sortListOfCacheItemResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults
 *
 * @desc Sorts an array of cache item results based on one of the properties
 *       of an item
 *
 * @param  {String}  sort_property - Name of property to sort 
 *                                   (i.e. 'element', 'violation' ..)
 * 
 * @param  {Number}  order         - If 1 sort ascending, else sort descending 
 *
 * @return  {Array} Sorted list of cache items 
 */

OpenAjax.a11y.formatters.TreeViewOfFilteredCacheItemResults.prototype.sortListOfCacheItemResults = function(sort_property, order) {

  function sortProperty(a, b) {
  
//    OpenAjax.a11y.logger.debug(" Group IDs: a=" + a.position  + " b=" + b.position);

    if (a[sort_property] > b[sort_property]) return  1 * order;
    if (a[sort_property] < b[sort_property]) return -1 * order;
    
    //tie breaker: name ascending is the second level sort
    
    if (sort_property != "violations_count") {
      if (a.violations_count > b.violations_count) return 1;
      if (a.violations_count < b.violations_count) return -1;
    }

    if (sort_property != "manual_checks_count") {
      if (a.manual_checks_count > b.manual_checks_count) return 1;
      if (a.manual_checks_count < b.manual_checks_count) return -1;
    }

    if (sort_property != "passed_count") {
      if (a.passed_count > b.passed_count) return 1;
      if (a.passed_count < b.passed_count) return -1;
    }
    
    if (sort_property != "hidden_count") {
      if (a.hidden_count > b.hidden_count) return 1;
      if (a.hidden_count < b.hidden_count) return -1;
    }

    if (sort_property != "position") {
      if (a.position > b.position) return 1;
      if (a.position < b.position) return -1;
    }
    

    return 0;  
  }
  
  if (typeof sort_property !== 'string') sort_property = 'position';
  if (typeof order !== 'number') order = 1;

//  OpenAjax.a11y.logger.debug("CACHE ITEM RESULTS SORT: prop=" + sort_property  + " order=" + order);

  var open_cache_item_list = this.getOpenCacheItemResults(true);

  open_cache_item_list.sort(sortProperty);

  return open_cache_item_list;

};

/**
 * @constructor TreeViewOfCacheItemResult
 *
 * @memberOf OpenAjax.a11y.formatters
 *
 * @desc Constructs a list of node results for a cache item result 
 *       that can be used by tree views (i.e. XUL custom tree views)
 *
 * @param  {CacheItemResult}  cache_item_result  - Filtered cache item result 
 *                                                 object for tree view presentation
 * 
 * @property {CacheItemResult}  cache_item_result  - Cache item result object
 *                                                   reference
 *
 * @property {Array}  node_result_items  -  An array of cache item result items
 *                                          for use be tree or list view rendering
 */

OpenAjax.a11y.formatters.TreeViewOfCacheItemResult = function(cache_item_result) {

  function addFilteredElementCount(count) {

    if (typeof count !== 'number' || count === 0) return;

    var str;

    if (count === 1) str = count + " " + cache_nls.filteredRule;
    else str = count + " "  + cache_nls.filteredRules;

    var row = { level : 0,
                container : false,
                open : false,
                
                node_result : null,
                position : -1,
                
                message : str,
                message_prop : "message"
        };

    nr_items.push(row);
  }

  function addProperty(nr_item, label, value) {
  
    var prop_item = { level     : 1,
                      container : false,
                      open      : false,

                      node_result : nr_item.node_result,
                      rule_number : rule_number,
                      position    : nr_item.position,

                      property_name      : label,  
                      property_name_prop : "",
                      value              : "",
                      value_prop         : "" 
                   };
 
    if (value !== null) {
      // if value is a string test to see if it is empty
      if (typeof value == "string") {
        if (value.length) {
          prop_item.value = value;
        } 
        else {
          prop_item.value      = cache_nls.empty;
          prop_item.value_prop = 'empty';
        }
      }
      else {
        prop_item.value = value.toString();
      }  
    }  
    else {
      prop_item.value = cache_nls.undefined;
      prop_item.value_prop = 'empty';
    }
    
    nr_item.properties.push(prop_item);    
        
  }

  var cache_nls = OpenAjax.a11y.cache_nls.getCacheNLS();

  var boolean_values = cache_nls.boolean_values;

  var node_results     = cache_item_result.filtered_node_results;
  var node_results_len = node_results.length;

  var nr_items = []; 

  for (var i = 0; i < node_results_len; i++) {
  
    var n_result =  node_results[i];
    
    var nr_item = { level       : 0,
                    container   : false,
                    open        : false,

                    node_result : n_result,

                    required_label : n_result.getRuleRequiredYesNo(),
                    required       : n_result.isRuleRequired(),

                    level_label  : n_result.getWCAG20Level(),
                    level_const  : n_result.getWCAG20LevelConstant(),
               
                    result_value : n_result.getResultValueConstant(),
                    result_label : n_result.getResultValue().label,
                    result_prop  : n_result.getResultValue().style,
               
                    rule_label   : n_result.getRuleSummary(), 
                    rule_prop    : "rule"
                 };
                        
   nr_items.push(nr_item);
  
  }
  
  if (typeof this.number_of_node_results_filtered === 'number') addFilteredElementCount(this.number_of_node_results_filtered);

  
  this.node_result_items = nr_items;

};

/**
 * @method setOpenState
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfCacheItemResult
 *
 * @desc Sets open state on all container items in the list
 *       Used for expanding or collapsing all leafs in a tree
 *
 * @param  {Boolean} flag  -  If true all container elements will be set 
 *                            to open, otherwise containers element set 
 *                            to close  
 */

OpenAjax.a11y.formatters.TreeViewOfCacheItemResult.prototype.setOpenState = function (flag) {

   function setContainers(list) {
   
     for (var i = 0; i < list.length; i++) {
   
        var item = list[i];
        
        if (item.container) {
          item.open = flag;
          
          if (item.properties && item.properties.length) setContainers(item.properties);
        }     
     }
   }
   
   if (typeof flag !== 'boolean') flag = true;
   
   setContainers(this.node_result_items);

};


/**
 * @method getOpenNodeResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfCacheItemResult
 *
 * @desc Returns an array of all the node result items, including the 
 *       items in the children property of items with open property set to true
 *
 * @return  {Array} Array of node result items optimized for a tree view
*/

OpenAjax.a11y.formatters.TreeViewOfCacheItemResult.prototype.getOpenNodeResults = function() {

  function getOpenItems(items) {
  
    if (typeof items !== 'object') return;
  
    var items_len = items.length;

    for (var i = 0; i < items_len; i++) {
    
      var item = items[i];
    
      list.push(item);
      
      if (item.open && item.properties && item.properties.length) { 
         getOpenItems(item.properties);
      }
    }      
  }
      
  var list = [];

  var nr_items = this.node_result_items;
  
  getOpenItems(nr_items);
  
  return list;
  
};

/**
 * @method sortListOfNodeResults
 *
 * @memberOf OpenAjax.a11y.formatters.TreeViewOfCacheItemResult
 *
 * @desc Sorts the list of node result items based on item property values
 *
 * @param  {String}  sort_property - Name of property to sort, property names 
 *                                   include: 'result', 'element', 'position'
 *
 * @param  {Number}  order         - If 1 sort ascending, else sort descending 
 *
 * @return  {Array} Array of objects optimized for display as a tree
 */

OpenAjax.a11y.formatters.TreeViewOfCacheItemResult.prototype.sortListOfNodeResults = function(sort_property, order) {

  function sortProperty(a, b) {
  
    if (a[sort_property] > b[sort_property]) return  -1 * order;
    if (a[sort_property] < b[sort_property]) return 1 * order;
    
    //tie breaker: name ascending is the second level sort
    
    if (sort_property != "result") {
      if (a.result > b.result) return -1;
      if (a.result < b.result) return 1;
    }

    if (sort_property != "level_const") {
      if (a.level_const > b.level_const) return -1;
      if (a.level_const < b.level_const) return 1;
    }

    
    return 0;  
  }

  return this.node_result_items.sort(sortProperty);

};



/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

OpenAjax.a11y.nls = OpenAjax.a11y.nls || {};

/* ---------------------------------------------------------------- */
/*                            DOMCacheNLS                          */
/* ---------------------------------------------------------------- */

/**
 * @constructor Cache
 *
 * @memberOf OpenAjax.a11y.nls
 *
 * @desc Constructs a DOMCache Object 
 *          
 * @property {String}  nls       - NLS cache items for properties
 */
 
OpenAjax.a11y.nls.Cache = function() {

  this.nls = {};

};

/**
 * @method addCacheNLSFromJSON()
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Constructs a DOMCache Object 
 *
 * @param  {locale}  locale         - Language code 
 * @param  {Object}  cache_nls_data - NLS cache items for properties
 */
 
OpenAjax.a11y.nls.Cache.prototype.addCacheNLSFromJSON = function(locale, cache_nls_data) {

  this.nls[locale] = cache_nls_data;

};

/**
 * @method getCacheNLS
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns the current cache nls object 
 *
 */
 
OpenAjax.a11y.nls.Cache.prototype.getCacheNLS = function() {

  return this.nls[OpenAjax.a11y.locale];

};

/**
 * @method getResultValueNLS
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Gets severity label, abbreviation, description and style 
 *
 * @param  {Number}  result_value  -  The constant representing the result of the 
 *                                    evaluation (i.e. violation, warning, passed...)
 *
 * @return {Object} Returns a object with the following properties: <br/>
 *                  'label'       : String representing the severity <br/>
 *                  'abbrev'      : Abbreviation string of the label<br/>
 *                  'description' : String describing the severity<br/>
 *                  'style'       : String that can used for styling the label<br/> 
 */
 
OpenAjax.a11y.nls.Cache.prototype.getResultValueNLS = function(result_value) {

  var obj = this.nls[OpenAjax.a11y.locale].rule_types[result_value];
  
  obj.toString = function () { return this.label; };

  return obj;
};

/**
 * @method getRuleCategory
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Get localized string for a rule category
 *
 * @param  {Number}  id  -  The constant representing the rule category
 *
 * @return {Object} Returns the NLS object for a rule category, empty object if not found
 *                  Properties of the object include: "title", "url" and "desc"
 */
 
OpenAjax.a11y.nls.Cache.prototype.getRuleCategory = function(id) {

  var rc = this.nls[OpenAjax.a11y.locale].rule_categories;
  var rc_num = rc.length;
  
  for (var i = 0; i < rc_num; i++) {
    if (rc[i].id === id) return rc[i];
  }
  
  var ro = {
    title : "Rule category with the the following ID not found: " + id,
    url   : "",
    desc  : ""
  };
  
  return ro;

};


/**
 * @method getLabelAndValueNLS
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns the label, human readable value and description of a cache property
 * 
 * @param  {String}           property  - The object property
 * @param  {String | Number}  value     - Current value of a property
 *
 * @return {Object} Returns object with three properties 'label', 'value' and 'description'
 */
 
OpenAjax.a11y.nls.Cache.prototype.getLabelAndValueNLS = function (property, value) {
  
    var info = {};  // return object 
    
    info.label       = property;
    info.value       = value;
    info.description = "";
    
    var nls_cache = this.nls[OpenAjax.a11y.locale];
    
    if (nls_cache) {

      var cp = nls_cache.resource_properties[property];
      
      // if null return default
      if (!cp) return info;

      if (cp.label)       info.label       = cp.label;
      if (cp.description) info.description = cp.description;        
      
      switch(typeof value) {
      
      case 'boolean': 
      
        if (value)
          info.value = nls_cache.boolean_values.true_value;
        else
          info.value = nls_cache.boolean_values.false_value;
        break;
        
      case 'number':
      
        if (cp.values) 
          info.value = cp.values[value].toString();
        else
          info.value = String(value);
          
        break; 

      default:
         break;
      }      
    } 
    
    return info;
  
};

/**
 * @method getLabelNLS
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns the label and description of a cache property
 *
 * @param  {String}  property  - The object property
 * 
 * @return {Object} Returns object with two properties 'label' and 'description'
 */
 
OpenAjax.a11y.nls.Cache.prototype.getLabelNLS = function (property) {
  
    var info = {};  // return object 
    
    info.label       = property;
    info.description = "";
    
    var nls_cache = this.nls[OpenAjax.a11y.locale];
    
    if (nls_cache) {
     
      var cp = nls_cache.resource_properties[property];
      
      // if null return default
      if (!cp) return info;

      if (cp.label)       info.label       = cp.label;
      if (cp.description) info.description = cp.description;        
              
    } 
    
    return info;
  
};

/**
 * @method getValueNLS
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns the value of a cache property
 *
 * @param  {String}           property  - The object property
 * @param  {String | Number}  value     - Current value of a property
 * 
 * @return {String} Returns string with the localized value of a property
 */
 
OpenAjax.a11y.nls.Cache.prototype.getValueNLS = function (property, value) {
  
    var str = "";  // return object 
       
    var nls_cache = this.nls[OpenAjax.a11y.locale];
    
    if (nls_cache) {

      var cp = nls_cache.resource_properties[property];
      
      // if null return default
      if (!cp) return value;

      switch(typeof value) {
      
      case 'boolean': 
      
        if (value)
          str = nls_cache.boolean_values.true_value;
        else
          str = nls_cache.boolean_values.false_value;
        break;
        
      case 'number':
      
        if (cp.values) 
          str = cp.values[value].toString();
        else
          str = String(value);
          
        break; 

      default:
         break;
      }      
              
    } 
    
    return str;
  
};

/**
 * @method getYesNoNLS
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Get a localized "Yes" or "No" string 
 *
 * @param {Boolean}  value  - A boolean value to get  string
 * 
 * @return {String} Returns 'Yes" if true, otherwise 'No' 
 */
 
OpenAjax.a11y.nls.Cache.prototype.getYesNoNLS = function (value) {
  
  var nls_cache = this.nls[OpenAjax.a11y.locale];
  
  if (typeof value !== 'boolean') return nls_cache.not_boolean_value;  
    
  if (value) return nls_cache.yes_no_values.yes_value;
    
  return nls_cache.yes_no_values.no_value;
  
};


/**
 * @method getNLSMissingLabelMessage
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns the missing form control label message and style
 * 
 * @return {String} Returns an object with a 'label' and 'style' property
 */
 
OpenAjax.a11y.nls.Cache.prototype.getNLSMissingLabelMessage = function () {
  
    var label_style;  // return object    
       
    var nls_cache = this.nls[OpenAjax.a11y.locale];    
    
    if (nls_cache) {
     
      label_style = nls_cache.missing_label;
      
      // if null return default
      if (!label_style) return "";
             
    } 
    
    return label_style;
  
};

/**
 * @method getNLSEmptyAltTextMessage
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns the empty alt text message message and style
 * 
 * @return {String} Returns an object with a 'label' and 'style' property
 */
 
OpenAjax.a11y.nls.Cache.prototype.getNLSEmptyAltTextMessage = function () {
  
    var label_style;  // return object    
       
    var nls_cache = this.nls[OpenAjax.a11y.locale];    
    
    if (nls_cache) {
     
      label_style = nls_cache.empty_alt_text;
      
      // if null return default
      if (!label_style) return "";
             
    } 
    
    return label_style;
  
};

/**
 * @method getNLSMissingAltMessage
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Returns an NLS localized 'missing alt attribute' message
 * 
 * @return {String} Returns an object with a 'label' and 'style' property
 */
 
OpenAjax.a11y.nls.Cache.prototype.getNLSMissingAltMessage = function () {
  
    var label_style;  // return object    
       
    var nls_cache = this.nls[OpenAjax.a11y.locale];    
    
    if (nls_cache) {
     
      label_style = nls_cache.missing_alt;
      
      // if null return default
      if (!label_style) return "";
             
    } 
    
    return label_style;
  
};


/**
 * @method addPropertyIfDefined
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Adds an item to a list of properties 
 */

OpenAjax.a11y.nls.Cache.prototype.addPropertyIfDefined = function (list, item, property) {

  if (typeof item[property] !== 'undefined') {
    list.push(this.getLabelAndValueNLS(property, item[property]));
  } // endif
  
};

/**
 * @method addInvalidAttribute
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Identifies an invalid attribute
 */

OpenAjax.a11y.nls.Cache.prototype.addInvalidAttribute = function (list, attribute) {

  var o = {};
  o.label = this.nls.invalid_attribute.label;
  o.value = attribute;
  o.style = this.nls.invalid_attribute.style;

  list.push(o);
  
};

/**
 * @method addInvalidValue
 *
 * @memberOf OpenAjax.a11y.nls.Cache
 *
 * @desc Identifies an invalid attribute value
 */

OpenAjax.a11y.nls.Cache.prototype.addInvalidValue = function (list, attribute, value) {

  var o = {};
  o.label = attribute;
  o.value = value + " " + this.nls.invalid_value.value;
  o.style = this.nls.invalid_value.style;

  list.push(o);
    
};
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

OpenAjax.a11y.nls = OpenAjax.a11y.nls || {};


/* ---------------------------------------------------------------- */
/*                       WCAG20                                     */
/* ---------------------------------------------------------------- */

/**
 * @constructor WCAG20
 *
 * @memberOf OpenAjax.a11y.nls
 *
 * @desc WCAG 2.0 information with properties with localized NLS values 
 *
 * @property  {Array}   nls - Associative array of WCAG 2.0 information 
 */

OpenAjax.a11y.nls.WCAG20 = function() {

  this.nls = {};
  
};

/**
 * @method addNLS
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20
 *
 * @desc Adds a localized version of WCAG 2.0 requirements to the cache 
 *
 * @param  {string}  locale  - Language code of WCAG 2.0  
 * @param  {Object}  nls     - Localized WCAG 2.0 object
 */

OpenAjax.a11y.nls.WCAG20.prototype.addNLS = function (locale, nls) {

  var item;
  var  p,  p_id,  np;  /* WCAG 2.0 Principle */
  var  g,  g_id,  ng;  /* WCAG 2.0 Guideline */
  var sc, sc_id, nsc;  /* WCAG 2.0 Success Criterion */

  // Validate the WCAG 2.0 NLS properties
  if (!nls.abbreviation) OpenAjax.a11y.logger.error("Missing abbreviation property for WCAG 2.0 with locale: " + locale);
  if (!nls.title)  OpenAjax.a11y.logger.error("Missing title property for WCAG 2.0 with locale: "              + locale);
  if (!nls.url)    OpenAjax.a11y.logger.error("Missing url property for WCAG 2.0 with locale: "                + locale);
  if (!nls.levels) OpenAjax.a11y.logger.error("Missing levels property for WCAG 2.0 with locale: "             + locale);
  if (!nls.evaluation_levels) OpenAjax.a11y.logger.error("Missing evaluation_levels property for locale: "     + locale);
  
  var wcag20 = new OpenAjax.a11y.nls.WCAG20NLS(locale, nls.abbreviation, nls.title, nls.url, nls.levels, nls.evaluation_levels);
  
 //  OpenAjax.a11y.logger.debug("WCAG 2.0 " + nls.title + " for " + locale); 
  
  if (!nls.principles || typeof nls.principles !== 'object') {
  
    OpenAjax.a11y.logger.debug("Missing principles object or not at an object for WCAG 2.0 with locale: " + locale);
    return;
    
  } else {
  
    for (p_id in nls.principles) {
    
      p = nls.principles[p_id];
      
//      OpenAjax.a11y.logger.debug("Principle " + p.title + " " + p.id); 
      
      np = new OpenAjax.a11y.nls.WCAG20NLSPrinciple(p_id, p);
      
      for (g_id in p.guidelines) {
      
        g = p.guidelines[g_id];
    
//        OpenAjax.a11y.logger.debug("  Guideline " + g.title + " " + g.id); 
      
        ng = new OpenAjax.a11y.nls.WCAG20NLSGuideline(np, g_id, g);

        for (sc_id in g.success_criteria) {
      
          sc = g.success_criteria[sc_id];
     
          nsc = new OpenAjax.a11y.nls.WCAG20NLSSuccessCriterion(np, ng, sc_id, sc);
          
//          OpenAjax.a11y.logger.debug("    Success Criteria " + nsc.sc_id + " (" + sc_id + "): " + sc.title); 
      
          ng.success_criteria.push(nsc); 
      
        } // end loop
        
        np.guidelines.push(ng); 
        
      } // end loop
      
      wcag20.principles.push(np); 
 
    } // end loop
  }
  
  this.nls[locale] = wcag20;
  
};

/**
 * @method getNLS
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20
 *
 * @desc Returns an object with a localized version of WCAG 2.0 requirements 
 *
 */

OpenAjax.a11y.nls.WCAG20.prototype.getNLS = function() {

  return this.nls[OpenAjax.a11y.locale];
  
};



/* ---------------------------------------------------------------- */
/*                       WCAG20NLS                                     */
/* ---------------------------------------------------------------- */

/**
 * @constructor WCAG20NLS
 *
 * @memberOf OpenAjax.a11y.nls
 *
 * @desc WCAG 2.0 information with properties with localized NLS values 
 *
 * @param  {String}  locale - Language code 
 * @param  {String}  abbrev - Localized abbreviation of WCAG 2.0 guidelines
 * @param  {String}  title  - Localized title of WCAG 2.0 guidelines 
 * @param  {String}  url    - URL to the translation of WCAG 2.0
 * @param  {Object}  levels - WCAG 2.0 levels for success criteria
 *
 * @property  {String}  locale - Language code 
 * @property  {String}  abbrev - Localized abbreviation of WCAG 2.0 guidelines
 * @property  {String}  title  - Localized title of WCAG 2.0 guidelines 
 * @property  {String}  url    - URL to the translation of WCAG 2.0
 * @property  {Object}  levels - WCAG 2.0 levels for success criteria
 *
 * @property  {Array}   principles - Array of WCAG 2.0 principle objects associated with the principle
 */

OpenAjax.a11y.nls.WCAG20NLS = function(locale, abbrev, title, url, levels, evaluation_levels) {

  this.locale = locale;    
  this.abbrev = abbrev;    
  this.title  = title;    
  this.url    = url;  
  this.levels = levels;
  this.evaluation_levels = evaluation_levels;
  
  this.principles = [];
  
};

/**
 * @method getNLSItemById
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLS
 *
 * @desc Returns an object with a localized version of WCAG 2.0 requirements 
 *
 * @param {String}  id  -  id for the wcag item to get NLS information
 *
 * @return {Object}  WCAG 2.0 NLS object
 */

OpenAjax.a11y.nls.WCAG20NLS.prototype.getNLSItemById = function(id) {

  for (var i = 0; i < this.principles.length; i++) {
  
     var p = this.principles[i];

//     OpenAjax.a11y.logger.debug("P Compare: " + p.principle_id + " " + id );

     if ((p.id === id) || (p.principle_id === id)) return p;     

     for (var j = 0; j < p.guidelines.length; j++) {
     
       var g = p.guidelines[j];

//       OpenAjax.a11y.logger.debug("  G Compare: " + g.guideline_id + " " + id );

       if ((g.id === id) || (g.guideline_id === id)) return g;
     
       for (var k = 0; k < g.success_criteria.length; k++ ) {
       
         var sc = g.success_criteria[k];

//         OpenAjax.a11y.logger.debug("  SC Compare: " + sc.success_criteria_id + " " + id );

         if ((sc.id === id) || (sc.sc_id === id)) return sc;
       
       } // end loop
     
     } // end loop
     
  } // end loop   
    
  return null;  
};


/**
 * @method getSuccessCriteriaLevel
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLS
 *
 * @desc Returns the success criteria 
 *
 * @param {String}  sc_id  -  String representing the success criteria id
 *
 * @return {Number}  Number representing the WCAG 2.0 success level 
 */

OpenAjax.a11y.nls.WCAG20NLS.prototype.getSuccessCriteriaLevel = function (sc_id) {

  var principles = this.principles;

  for (i = 0; i < principles.length; i++) {

    var p = wcag20_nls.principles[i];

    for (j = 0; j < p.guidelines.length; j++) {

      var g = p.guidelines[i];

      for (k = 0; k < g.success_criteria.length; k++) {
      
        var sc = g.success_criteria[i];
        
        if (sc.sc_id === sc_id) return sc.level;

      }
    }
  }

  return OpenAjax.a11y.WCAG20_LEVEL.UNKNOWN;

};



/**
 * @method getNLSWCAG20Level
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLS
 *
 * @desc Returns an NLS localized version of WCAG 2.0 success criterion level 
 *
 * @param {Number}  level  -  Numerical constant defined in OAA cache representing the WCAG 2.0 success criterion level
 */

OpenAjax.a11y.nls.WCAG20NLS.prototype.getNLSWCAG20Level = function (level) {

  if (typeof this.levels[level] !== 'string') OpenAjax.a11y.logger.debug("  Level: " + level);


  return this.levels[level];
  
};


/**
 * @method getEvaluationLevelsNLS
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLS
 *
 * @desc Returns an NLS localized version of WCAG 2.0 success criterion levels 
 *
 * @param {Number}  levels  -  Numerical constant defined in OAA cache representing the evaluation levels
 *
 * @return {String} String representing the evaluation levels (i.e A, A and AA, A, AA and AAA)
 */

OpenAjax.a11y.nls.WCAG20NLS.prototype.getEvaluationLevelsNLS = function (levels) {

  return this.evaluation_levels[levels];
  
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLS
 *
 * @desc Returns an nls JSON representation of wcag 2.0 information
 *
 * @param {String} prefix  -  A prefix string typically spaces
 * 
 * @return {String}  JSON formatted string 
 */

OpenAjax.a11y.nls.WCAG20NLS.prototype.toJSON = function(prefix) {

  var next_prefix = "";

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  else next_prefix = prefix + "  ";
  
  var json = "";
  
  json += "{";
 
  for (var i = 0; i < this.principles.length; i++) json += this.principles[i].toJSON(next_prefix);
  
  json += prefix + "}";

  return json;
};

/* ---------------------------------------------------------------- */
/*                       WCAG20NLSPrinciple                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor WCAG20NLSPrinciple
 *
 * @memberOf OpenAjax.a11y.nls
 *
 * @desc WCAg 2.0 Principle information with properties with localized NLS values 
 *
 * @param  {Object}  principle_id  - Principle id
 * @param  {Object}  info          - Principle information
 *
 * @property  {String}  principle_id  - Principle id 
 * @property  {String}  title         - Title of the principle 
 * @property  {String}  description   - Description of principle 
 * @property  {String}  url_spec      - URL to information on the requirement
 *
 * @property  {Array}   guidelines - Array of WCAG 2.0 guideline objects associated with the principle
 */

OpenAjax.a11y.nls.WCAG20NLSPrinciple = function(principle_id, info) {

  this.principle_id = principle_id;    // Text string
  this.id           = info.id;         // Number
  this.title        = info.title;    
  this.description  = info.description;    
  this.url_spec     = info.url_spec;   
  
  this.guidelines = [];
  
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLSPrinciple
 *
 * @desc Returns an nls JSON representation of wcag 2.0 principle information
 *
 * @param {String} prefix  -  A prefix string typically spaces
 * 
 * @return {String}  JSON formatted string 
 */

OpenAjax.a11y.nls.WCAG20NLSPrinciple.prototype.toJSON = function(prefix) {

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  
  var json = "";
       
  json += prefix + "\"" + this.principle_id + "\" : { "; 

  json += prefix + "  \"id\"             : " + this.id + ","; 
  json += prefix + "  \"type\"           : \"p\","; 
  json += prefix + "  \"title\"          : \"" + OpenAjax.a11y.util.escapeForJSON(this.title) + "\","; 
  json += prefix + "  \"description\"    : \"" + OpenAjax.a11y.util.escapeForJSON(this.description) + "\","; 
  json += prefix + "  \"url\"            : \"" + OpenAjax.a11y.util.escapeForJSON(this.url_spec) + "\""; 

  json += prefix + "},"; 
    
  for (var i = 0; i < this.guidelines.length; i++) json += this.guidelines[i].toJSON(prefix);
  
  return json;
};


/* ---------------------------------------------------------------- */
/*                       WCAG20NLSGuideline                           */
/* ---------------------------------------------------------------- */

/**
 * @constructor WCAG20NLSGuideline
 *
 * @memberOf OpenAjax.a11y.nls
 *
 * @desc WCAg 2.0 Guideline information with properties with localized NLS values 
 *
 * @param  {WCAG20NLSPrinciple}  principle     - Principle object reference 
 * @param  {String}              guideline_id  - Guideline ID
 * @param  {Object}              info          - Guideline information object
 *
 * @property  {WCAG20NLSPrinciple}  principle  - Principle object reference 
 *
 * @property  {String}  guideline_id  - Guideline id 
 * @property  {String}  title         - Title of the guideline 
 * @property  {String}  description   - Description of the guideline 
 * @property  {String}  url_spec      - URL to information on the guideline requirement
 *
 * @property  {Array}   success_criteria  - Array of WCAG 2.0 success criteria objects associated with the principle
 */

OpenAjax.a11y.nls.WCAG20NLSGuideline = function(principle, guideline_id, info) {

  this.principle     = principle;    
  
  this.guideline_id  = guideline_id;   
  this.id            = info.id;         // Number
  
  this.title         = info.title;    
  this.description   = info.description;    
  this.url_spec      = info.url_spec;   
  
  this.success_criteria = [];
  
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLSGuideline
 *
 * @desc Returns an nls JSON representation of wcag 2.0 guideline information
 *
 * @param {String} prefix  -  A prefix string typically spaces
 * 
 * @return {String}  JSON formatted string 
 */

OpenAjax.a11y.nls.WCAG20NLSGuideline.prototype.toJSON = function(prefix) {

  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  
  var json = "";
       
  json += prefix + "\"" + this.guideline_id + "\" : { "; 

  json += prefix + "  \"id\"             : " + this.id + ","; 
  json += prefix + "  \"type\"           : \"g\","; 
  json += prefix + "  \"title\"          : \"" + OpenAjax.a11y.util.escapeForJSON(this.title) + "\","; 
  json += prefix + "  \"description\"    : \"" + OpenAjax.a11y.util.escapeForJSON(this.description) + "\","; 
  json += prefix + "  \"url\"            : \"" + OpenAjax.a11y.util.escapeForJSON(this.url_spec) + "\""; 

  json += prefix + "},"; 
    
  for (i = 0; i < this.success_criteria.length; i++) json += this.success_criteria[i].toJSON(prefix);
  
  return json;
};



/* ---------------------------------------------------------------- */
/*                       WCAG20NLSSuccessCriterion                    */
/* ---------------------------------------------------------------- */

/**
 * @constructor WCAG20NLSSuccessCriterion
 *
 * @memberOf OpenAjax.a11y.nls
 *
 * @desc  WCAG 2.0 Success Criteria information with properties with localized NLS values 
 *
 * @param  {WCAG20NLSPrinciple}  principle  - Principle object reference 
 * @param  {WCAG20NLSGuideline}  guideline  - Guideline object reference
 * @param  {String}              sc_id      - Success criterion ID
 * @param  {Object}              info       - Success criterion information object
 * 
 * @property  {WCAG20NLSPrinciple}  principle  - Principle object reference 
 * @property  {WCAG20NLSGuideline}  guideline  - Guideline object reference
 *
 * @property  {String}  sc_id          - Success criterion ID
 * @property  {String}  title          - Title of the success criterion 
 * @property  {String}  level          - Level of importance of a success criterion
 * @property  {String}  url_spec       - URL to information on the success criteria requirement
 * @property  {String}  url_meet       - URL to information on how to meet the success criteria requirements
 * @property  {String}  url_understand - URL to information on how to understand the success criteria requirements
 * @property  {Array}   resources      - Other information related to the success criterion
 */

OpenAjax.a11y.nls.WCAG20NLSSuccessCriterion = function(principle, guideline, sc_id, info) {

  this.principle  = principle;    
  this.guideline  = guideline;    
  
  this.sc_id        = sc_id;      // String   
  this.id           = info.id;    // Number
  
  this.level          = info.level;   
  this.title          = info.title;    
  this.description    = info.description;    
  this.url_spec       = info.url_spec;   
  this.url_meet       = info.url_meet;   
  this.url_understand = info.url_understand;   
  
  this.resources = [];  
  
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.nls.WCAG20NLSSuccessCriterion
 *
 * @desc Returns an nls JSON representation of wcag 2.0 success criterion information
 *
 * @param {String} prefix  -  A prefix string typically spaces
 * 
 * @return {String}  JSON formatted string 
 */

OpenAjax.a11y.nls.WCAG20NLSSuccessCriterion.prototype.toJSON = function(prefix) {

  function getNLSLevel(level) {
  
    if (level === OpenAjax.a11y.WCAG20_LEVEL.A) return "A";
    if (level === OpenAjax.a11y.WCAG20_LEVEL.AA) return "AA";
    if (level === OpenAjax.a11y.WCAG20_LEVEL.AAA) return "AAA";
  
    return "unknown";
  }
  
  if (typeof prefix !== 'string' || prefix.length === 0) prefix = "";
  
  var json = "";
       
  json += prefix + "\"" + this.sc_id + "\" : { "; 

  json += prefix + "  \"id\"             : " + this.id + ","; 
  json += prefix + "  \"type\"           : \"sc\","; 
  json += prefix + "  \"level\"          : \"" + getNLSLevel(this.level) + "\","; 
  json += prefix + "  \"title\"          : \"" + OpenAjax.a11y.util.escapeForJSON(this.title) + "\","; 
  json += prefix + "  \"description\"    : \"" + OpenAjax.a11y.util.escapeForJSON(this.description) + "\","; 
  json += prefix + "  \"url\"            : \"" + this.url_spec + "\","; 
  json += prefix + "  \"url_meet\"       : \"" + this.url_meet + "\","; 
  json += prefix + "  \"url_understand\" : \"" + this.url_understand + "\""; 

  if (this.sc_id === '4.1.2') json += prefix + "}"; 
  else json += prefix + "},"; 
    
  return json;
};

/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


/* ---------------------------------------------------------------- */
/*                       Rulesets                                   */
/* ---------------------------------------------------------------- */

/**
 * @constructor Rulesets
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc Information on the rulesets available for evauating documents 
 *
 * @property  {Array}   rulesets - Associative array of rulesets  
 */

OpenAjax.a11y.Rulesets = function() {

  this.rulesets = [];
  
};

/**
 * @method addRuleset
 *
 * @memberOf OpenAjax.a11y.Rulesets
 *
 * @desc Adds a localized version of WCAG 2.0 requirements to the cache 
 *
 * @param  {string}  type        - Type of ruleset (WCAG 2.0 ruleset is the only type currently supported)  
 * @param  {Object}  rulesetdata - JSON object containing the ruleset information
 */

OpenAjax.a11y.Rulesets.prototype.addRuleset = function(type, ruleset_data) {

  switch (type) {
  
  case 'WCAG20':
    var rs = new OpenAjax.a11y.Ruleset(ruleset_data);
    
    if (rs) {
      this.rulesets.push(rs);
      OpenAjax.a11y.logger.info("  Ruleset '" + rs.ruleset_title + "' is loaded");
    }  
    else {
      OpenAjax.a11y.logger.error("  ** Error loading ruleset: " + ruleset_data[id]);
    }  
    break;
    
  default:
    OpenAjax.a11y.logger.error("  ** Rule set type: '" + type + "' not supported");
    break;
  }

};

/**
 * @method getRuleset
 *
 * @memberOf OpenAjax.a11y.Rulesets
 *
 * @desc Gets a ruleset with the specified ID  
 *
 * @param  {string}  ruleset_id  - Ruleset id of the ruleset to return
 */

OpenAjax.a11y.Rulesets.prototype.getRuleset = function(ruleset_id) {

  var i;
  
  for (i = 0; i < this.rulesets.length; i++ ) {
     if (this.rulesets[i].ruleset_id === ruleset_id) return this.rulesets[i]; 
  }

  // if ruleset_id is not defined, return the first ruleset, if it is defined
  if (this.rulesets[0]) return this.rulesets[0];
  
  return null;
};

/**
 * @method getAllRulesets
 *
 * @memberOf OpenAjax.a11y.Rulesets
 *
 * @desc Gets NLS ruleset information for all rulesets  
 *
 * @returns  {Array} Array of objects that contain NLS information about a ruleset
 */

OpenAjax.a11y.Rulesets.prototype.getAllRulesets = function() {

  var rulesets = [];
  
  for (var i = 0; i < this.rulesets.length; i++) {  
    rulesets.push(this.rulesets[i]);
  }

  return rulesets;
};

/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.Rulesets
 *
 * @desc Returns a JSON representation of the rule
 *
 * @param  {String}  prefix     - String of leading spaces for formatting JSON output (Optional) 
 *
 * @return  {String}  Returns a JSON representation of the rule
 */
OpenAjax.a11y.Rulesets.prototype.toJSON = function (prefix) {

  if (typeof prefix !== 'string') prefix = "";

  var json = "";
  
  json += prefix + "[\n";

  var rulesets_len  = this.rulesets.length;
  var rulesets_last = rulesets_len - 1;
  
  for (var i = 0; i < this.rulesets.length; i++) { 
    var ruleset = this.rulesets[i];
    
    json += prefix + "{\n";
    json += prefix + "  \"ruleset_id\"    : \"" + ruleset.ruleset_id   + "\",\n";  
    json += prefix + "  \"version\"       : \"" + ruleset.ruleset_version      + "\",\n";
    json += prefix + "  \"last_updated\"  : \"" + ruleset.ruleset_updated + "\",\n";
    json += prefix + "  \"title\"         : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset.ruleset_title) + "\",\n";
    json += prefix + "  \"author_name\"   : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset.ruleset_author_name) + "\",\n";
    json += prefix + "  \"author_url\"    : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset.ruleset_author_url) + "\",\n";
    json += prefix + "  \"description\"   : \"" + OpenAjax.a11y.util.escapeForJSON(ruleset.ruleset_description) + "\",\n";
    json += prefix + "  \"rule_mappings\" : {\n";

    var rule_mappings      = ruleset.rule_mappings;
    var rule_mappings_len  = rule_mappings.length;
    var rule_mappings_last = rule_mappings_len - 1;

    for (var j = 0; j < rule_mappings_len; j++) {
      var rule_mapping = rule_mappings[j];
      
      json += prefix + "    \"" + rule_mapping.rule.rule_id + "\" : {\n";
      if (rule_mapping.required) json += prefix + "                \"required\"    : true,\n";
      else json += prefix + "                \"required\"    : false,\n";
      json += prefix + "                \"enabled\" : " + rule_mapping.enabled      + "\n";
      json += prefix + "              }";
      
      if (j != rule_mappings_last) json += ",\n";
      else json += "\n";
    }

    json += prefix + "  }\n";

    json += prefix + "}";
    if (i != rulesets_last) json += ",\n";
  }
  
  json += prefix + "]\n";

  return json;
};



/* ---------------------------------------------------------------- */
/*                       Ruleset                              */
/* ---------------------------------------------------------------- */

/**
 * @constructor Ruleset
 *
 * @memberOf OpenAjax.a11y
 *
 * @param  {Object} ruleset_data  -  JSON object representing rule mapping
 *
 * @example
 *
 * function updateProgess(message, percent) {
 *
 *  .....
 *
 * }
 *
 * var url   = window.location.href;
 * var title = window.title;
 * var doc   = window.document;
 *
 * var ruleset = OpenAjax.a11y.all_rulesets.getRuleset('WCAG20_TRANS');
 * var result  = ruleset.evaluate(url, title, doc, updateProgress, true);
 *
 */ 

/**
 * @private
 * @constructor Internal Properties
 * NOTE: The following properties are defined when the ruleset is loaded
 *
 * @property {String} type                 - String representing the type of ruleset (i.e. WCAG20)          
 * @property {String} ruleset_id           - id of the ruleset           
 * @property {String} ruleset_title        - NLS localized title of the ruleset           
 * @property {String} ruleset_description  - Description of the ruleset         
 * @property {String} ruleset_author_name  - Name of the author(s) or organization         
 * @property {String} ruleset_author_url   - URL to the author(s) or organization       
 * @property {String} ruleset_updated      - Last time the ruleset was updated     
 * @property {Number} number_of_rules      - number of rules in the rule set
 *
 * @property {Array}  rule_mappings - Array of RuleMapping objects          
 *
 * @property {Number}   evaluation_levels          - Level of WCAG 2.0 Success Criteria to evaluate (i.e. A, AA, AAA)
 * @property {Boolean}  recommended_rules_enabled  - If true recommended rules are evaluated
 *
 * NOTE: The following properties are defined after each evaluation
 *
 * @property {String} eval_title  - The title of the document evaluated
 * @property {String} eval_url    - The url of the ldocument evaluated
 * @property {String} eval_date   - Date of the evaluation
 *
 * @property {Object} doc          - Reference to browser document object model (DOM) that holds the document to be analyzed
 * @property {Object} wcag20_nls   - Reference to WCAG 2.0 NLS object for current language
 * @property {Object} dom_cache    - Reference to DOMCache object
 *
 * @property {Array}  rule_results   - Array of rule result objects 
 * @property {Array}  rule_mappings  - Array of rule mapping objects (i.e. required or recommended rules) 
 */
 
OpenAjax.a11y.Ruleset = function (ruleset_data) {

  var i;
 
  this.type = "WCAG20";
  this.ruleset_title = {};
  
  this.ruleset_id         = ruleset_data['ruleset_id'];
  this.ruleset_version    = ruleset_data['version'];
  this.evaluation_levels  = OpenAjax.a11y.EVALUATION_LEVELS.A_AA;
  
  this.recommended_rules_enabled = true;

  this.required_count    = 0;
  this.recommended_count = 0;

  this.rule_results = [];

  var id       = ruleset_data['ruleset_id'];
  var title    = "";
  var url      = "";
  var desc     = "";
  var auth     = "";
  var auth_url = "";
  var date     = "0000-00-00";
  var ver      = ruleset_data['version'];

  // Check for ruleset id

  if (ruleset_data['ruleset_id']) {
    this.ruleset_id  = ruleset_data['ruleset_id'];
    OpenAjax.a11y.logger.info("Loading ruleset with the id: " + this.ruleset_id);
  } 
  else {
    OpenAjax.a11y.logger.error("  ** Ruleset missing id");
    return null;
  }

  // Check for ruleset version

  if (ruleset_data['version']) {
    ver  = ruleset_data['version'];
    this.ruleset_version  = ver;    
  } 
  else {
    OpenAjax.a11y.logger.error("  ** Ruleset missing version");
    return null;
  }

  // Check for default and localized ruleset title

  if (ruleset_data.title && ruleset_data.title['default']) {
    title = ruleset_data.title['default'];
    
    // get localized name for ruleset
    
    if (ruleset_data.title[OpenAjax.a11y.locale]) {
      title = ruleset_data.title[OpenAjax.a11y.locale];
    }

    this.ruleset_title = title;

  } 
  else {
    OpenAjax.a11y.logger.error("  ** Ruleset " + this.ruleset_id + " missing default title");
    return null;
  }

  // Check for ruleset last updated property 

  if (ruleset_data['last_updated']) {
    date  = ruleset_data['last_updated'];
    this.ruleset_updated  = date;
  } 
  else {
    OpenAjax.a11y.logger.warning("  ** Ruleset missing last updated date, set to null");
    this.ruleset_updated  = "0000-00-00";
  }

  // Check for default and localized ruleset descriptions

  if (ruleset_data.description && ruleset_data.description['default']) {
    desc = ruleset_data.description['default'];
    
    // get localized name for ruleset
    
    if (ruleset_data.description[OpenAjax.a11y.locale]) {
      desc = ruleset_data.description[OpenAjax.a11y.locale];
    }
    
    this.ruleset_description = desc;
  } 
  else {
    OpenAjax.a11y.logger.error("  ** Ruleset " + this.ruleset_id + " missing default description");
    this.ruleset_description = "no description";
  }

  // Check for default and localized ruleset descriptions

  if (ruleset_data.author && ruleset_data.author.name) {
    auth = ruleset_data.author.name;
    
    if (typeof ruleset_data.author.url === 'string') auth_url = ruleset_data.author.url;

    this.ruleset_author_name = auth;
    this.ruleset_author_url  = auth_url;    

//    OpenAjax.a11y.logger.debug("  Ruleset Author: " + this.ruleset_author_name);
//    OpenAjax.a11y.logger.debug("  Ruleset URL: " + this.ruleset_author_url);
  } 
  else {
    OpenAjax.a11y.logger.error("  ** Ruleset " + this.ruleset_id + " missing author information");
    auth = "no author";
    this.ruleset_author_name = auth;
    this.ruleset_author_url  = "";
  }

  this.number_of_rules   = 0;

  this.rule_mappings = [];

  var rule_mappings_from_data = ruleset_data['rule_mappings'];
  
  var rm_new;

  if (rule_mappings_from_data) {

    for (var rule_id in rule_mappings_from_data) {
  
      var rule_mapping = rule_mappings_from_data[rule_id];
  
      if (typeof rule_mapping.required === 'boolean') {
    
        if (typeof rule_mapping.enabled === 'boolean') {
          rm_new = new OpenAjax.a11y.RuleMapping(rule_id, rule_mapping.required, rule_mapping.enabled);
        } 
        else { 
          rm_new = new OpenAjax.a11y.RuleMapping(rule_id, rule_mapping.required, true);
        }
        
        if (rm_new && rm_new.rule) {
          this.rule_mappings.push(rm_new);
      
          this.number_of_rules++;

          if (rule_mapping.required) this.required_count++;   
          else this.recommended_count++;   

        }
      } 
      else {
        OpenAjax.a11y.logger.error("        ** Ruleset rule " + rule_id + " is missing valid 'type' property");              
      }    
    } // end loop
  }
  else {
    OpenAjax.a11y.logger.info("  ** Ruleset " + this.ruleset_id + " does not have any rules");
  }
  
  // local references to current NLS information, based on current locale setting
  
  this.wcag20_nls = OpenAjax.a11y.all_wcag20_nls.getNLS();      

  this.ruleset_information = OpenAjax.a11y.info.RulesetInfo(id, title, url, desc, this.required_count, this.recommended_count, auth, auth_url, date, ver);

  return this;
  
};

/**
 * @method getRulesetInfo
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return ruleset of information 
 *
 * @return {RulesetInfo}  RulesetInfo object
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetInfo = function () {
  return this.ruleset_information;
};

/**
 * @method getRulesetId
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return id of the ruleset 
 *
 * @return {String}  String representing the ruleset ID
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetId = function () {
  return this.ruleset_id;
};

/**
 * @method getRulesetTitle
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return title of the ruleset
 *
 * @return {String}  String representing the NLS title
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetTitle = function () {
  return this.ruleset_title;
};

/**
 * @method getRulesetVersion
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return version of the ruleset
 *
 * @return {String}  String representing the version
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetVersion = function () {
  return this.ruleset_version;
};

/**
 * @method getRulesetDescription
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return the description of the ruleset
 *
 * @return {String}  String representing the version
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetDescription = function () {
  return this.ruleset_description;
};

/**
 * @method getRulesetAuthor
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return the author of the ruleset
 *
 * @return {String}  String representing the author
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetAuthor = function () {
  return this.ruleset_author_name;
};

/**
 * @method getRulesetAuthorURL
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return the author url of the ruleset
 *
 * @return {String}  String representing the author url
 */
 
OpenAjax.a11y.Ruleset.prototype.getRulesetAuthorURL = function () {
  return this.ruleset_author_url;
};


/**
 * @method getNumberOfRequiredRules
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return number of required rules
 *
 * @return {Number}  Number of required rules in the ruleset
 */
 
OpenAjax.a11y.Ruleset.prototype.getNumberOfRequiredRules = function () {
  return this.required_count;
};

/**
 * @method getNumberOfRecommendedRules
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return number of recommended rules
 *
 * @return {Number}  Number of recommended rules in the ruleset
 */
 
OpenAjax.a11y.Ruleset.prototype.getNumberOfRecommendedRules = function () {
  return this.recommended_count;
};

/**
 * @method getNumberOfRules
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Return total number of rules
 *
 * @return {Number}  Total number of rules in the ruleset
 */
 
OpenAjax.a11y.Ruleset.prototype.getNumberOfRules = function () {
  return this.number_of_rules;
};

/**
 * @method setEvaluationLevels
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Enable and disable rules based on the WCAG level 
 *
 * @param  {Number}    level   - Level to success criteria to test
 */
 
OpenAjax.a11y.Ruleset.prototype.setEvaluationLevels = function (level) {

  this.evaluation_levels = level;
  
};

/**
 * @method getEvaluationLevelsConstant
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Get WCAG levels to evaluate 
 *
 * @return  {Number}  Number represents the levels of success criteria to test
 */
 
OpenAjax.a11y.Ruleset.prototype.getEvaluationLevelsConstant = function () {

  return this.evaluation_levels;
  
};

/**
 * @method getEvaluationLevels
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Get NLS WCAG levels to evaluate 
 *
 * @return  {String}  String representing the levels of success criteria evaluated
 */
 
OpenAjax.a11y.Ruleset.prototype.getEvaluationLevels = function () {

  var nls_wcag20  = OpenAjax.a11y.all_wcag20_nls.getNLS();  

  return nls_wcag20.getEvaluationLevelsNLS(this.evaluation_levels);
  
};


/**
 * @method setRecommendedRulesEnabled
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Enable and disable evaluation of recommeneded rules 
 *
 * @param  {Boolean}    enabled   - True to evaluate recommended rules, False if not to evaluated recommeneded rules
 */
 
OpenAjax.a11y.Ruleset.prototype.setRecommendedRulesEnabled = function (enabled) {

  this.recommended_rules_enabled = enabled;
  
};

/**
 * @method getRecommendedRulesEnabled
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Get the state of the evaluation of recommeneded rules 
 *
 * @return  {Boolean} True if evaluation included recommended rules, False if not to evaluate recommeneded rules
 */
 
OpenAjax.a11y.Ruleset.prototype.getRecommendedRulesEnabled = function () {

  return this.recommended_rules_enabled;
  
};


/**
 * @method setBrokenLinkTesting
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Enable and disable the cache from testing for broken urls 
 *
 * @param  {Boolean}  broken_links   - If true enables cache to test for broken links
 */
 
OpenAjax.a11y.Ruleset.prototype.setBrokenLinkTesting = function (broken_links) {
  
  OpenAjax.a11y.URL_TESTING_ENABLED = broken_links;
  
};

/**
 * @method getBrokenLinkTesting
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Get whether broken link testing is enabled or disabled  
 *
 * @return  {Boolean}  True if broken link testing is enabled
 */
 
OpenAjax.a11y.Ruleset.prototype.getBrokenLinkTesting = function () {
  
  return OpenAjax.a11y.URL_TESTING_ENABLED;
  
};

/**
 * @method setDataTableAssumption
 * @deprecated
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Set whether table markup should be assumed to be used for layout or tabular data
 *       The assumption is used when no indicators of layout (i.e. role="presentation") or
 *       tabular data (i.e. no TH cells, summary attributes, headers attributes ....) are
 *       found in the markup 
 *
 * @param  {Boolean}  assumption   - If false asssumes tables are used for layout; otherwise assumes tabular data 
 */
 
OpenAjax.a11y.Ruleset.prototype.setDataTableAssumption = function (assumption) {
  
  OpenAjax.a11y.DATA_TABLE_ASSUMPTION = assumption;
  
};

/**
 * @method getDataTableAssumption
 * @deprecated
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Get whether table markup should be assumed to be used for layout or tabular data 
 *       The assumption is used when no indicators of layout (i.e. role="presentation") or
 *       tabular data (i.e. no TH cells, summary attributes, headers attributes ....) are
 *       found in the markup 
 *
 * @return  {Boolean}  if false table markup is assumed to be for layout; if true the assumption is tabular data 
 */
 
OpenAjax.a11y.Ruleset.prototype.getDataTableAssumption = function () {
  
  return OpenAjax.a11y.DATA_TABLE_ASSUMPTION;
  
};


/**
 * @method setLayoutTableAssumption
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Set whether table markup should be assumed to be used for layout or tabular data
 *       The assumption is used when no indicators of layout (i.e. role="presentation") or
 *       tabular data (i.e. no TH cells, summary attributes, headers attributes ....) are
 *       found in the markup 
 *
 * @param  {Boolean}  assumption   - If true asssumes table markup is used for layout; if false  unless header cells or other indicator of a data table is found
 */
 
OpenAjax.a11y.Ruleset.prototype.setLayoutTableAssumption = function (assumption) {
  
  OpenAjax.a11y.DATA_TABLE_ASSUMPTION = !assumption;
  
};

/**
 * @method getLayoutTableAssumption
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Set whether table markup should be assumed to be used for layout or data table 
 *       The assumption is used when no indicators of layout (i.e. role="presentation") or
 *       tabular data (i.e. no TH cells, summary attributes, headers attributes ....) are
 *       found in the markup 
 *
 * @return  {Boolean}  if true table markup is assumed to be for layout; if false the table markup is assumed to be for tabular data 
 */
 
OpenAjax.a11y.Ruleset.prototype.getLayoutTableAssumption = function () {
  
  return !OpenAjax.a11y.DATA_TABLE_ASSUMPTION;
  
};



/**
 * @method setEventHandlerProcessor
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Set which process
 *
 * @param  {String}  option   - The event handler process to use to identify events on a DOM element
 *                              Options currently implemented:
 *                              firefox
 *                              fae-util
 * 
 *                              Any other option will result in no event handler processing
 */
 
OpenAjax.a11y.Ruleset.prototype.setEventHandlerProcessor = function (option) {
  
  option = option.toLowerCase();
  
  switch (option) {
  
  case 'firefox':
  
    OpenAjax.a11y.EVENT_HANDLER_PROCESSOR = option;
    break;
    
  case 'fae-util':

    OpenAjax.a11y.EVENT_HANDLER_PROCESSOR = option;
    break;
  
  default:
    
    OpenAjax.a11y.EVENT_HANDLER_PROCESSOR = "none";
    break;
  
  }
  
};

/**
 * @method getEventHandlerProcessor
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Get the current option for event handler processining
 *
 * @return  {String}  Returns the current setting of the option for even handler processing
 */
 
OpenAjax.a11y.Ruleset.prototype.getEventHandlerProcessor = function () {
  
  return OpenAjax.a11y.EVENT_HANDLER_PROCESSOR;
  
};

/**
 * @method evaluate
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Evaluate a document using the OpenAjax ruleset and return an evaluation object 
 *
 * @param  {String}    url       - url of document being analyzed    
 * @param  {String}    title     - Title of document being analyzed    
 * @param  {Object}    doc       - Browser document object model (DOM) to be evaluated    
 *
 * @param  {Object}    progress_callback  - Progress call back function (NOTE: not used right now)    
 * @param {Boolean} build_cache When true will build all cache in one tranversal of the DOMElements cache, when false specialized caches will be built when they are need by a rule
 *
 */
 
OpenAjax.a11y.Ruleset.prototype.evaluate = function (url, title, doc, progress_callback, build_cache) {
      
  var PROGRESS = OpenAjax.a11y.PROGRESS;

  // OpenAjax.a11y.logger.debug("Starting evaluation: " + this.ruleset_id + " " + this.default_name + " " + this.number_of_rules + " rules" );
  
  var dom_cache = new OpenAjax.a11y.cache.DOMCache(url, title, doc);      

  dom_cache.updateDOMElementCache();
  
  if (build_cache) { 
    dom_cache.updateAllCaches();
  }  

  var evaluation_result = new OpenAjax.a11y.EvaluationResult(this, title, url, doc, dom_cache);

  var rule_mappings = this.rule_mappings;
  var rule_mappings_len = rule_mappings.length;

  OpenAjax.a11y.logger.info("Starting evaluation....");
  OpenAjax.a11y.logger.info("    URL: " + url);  
  OpenAjax.a11y.logger.info("RULESET: " + this.ruleset_id);  

  for (var i = 0; i < rule_mappings_len; i++) {
    
    var rule_mapping = rule_mappings[i];
    var rule = rule_mapping.rule;
    var rule_definition  = rule.getRuleDefinition(rule_mapping.required);                     

    if (rule_mapping) {

      var rule_result = new OpenAjax.a11y.RuleResult(rule_mapping); 

//      OpenAjax.a11y.logger.debug("Recommended Rules Enabled: " + this.recommended_rules_enabled);
//      OpenAjax.a11y.logger.debug("Rule: " + rule.rule_id + "  Enabled: " + rule_mapping.enabled  + "  Required: " + rule_mapping.required + "  WCAG 2.0 Level: " + rule.getWCAG20LevelConstant() + " Ruleset level: " + this.evaluation_levels + " test: " + (rule.getWCAG20LevelConstant() & this.evaluation_levels));

      if (rule &&
          rule_mapping.enabled && 
          (rule_mapping.required ||
           this.recommended_rules_enabled) &&
          (rule.getWCAG20LevelConstant() & this.evaluation_levels)) {      

        rule_result.rule_evaluated = true;

        // Check to see if the specialized cache needed for the rule is already 
        // If not create the specialized cache
                     
        if (!build_cache ) {
          var up_to_date = dom_cache.isUpToDate(rule.cache_dependency);
          if (up_to_date.exists) {
            if(!up_to_date.up_to_date) dom_cache.updateCache(rule.cache_dependency);
          } 
        } 

        if (rule.language_dependency.length) {
          // Rules with a language restriction
          if (rule.language_dependency.indexOf(OpenAjax.a11y.locale) >= 0) {
            rule.validate(dom_cache, rule_result);
          }
        }
        else {
          // Rules without any language restrictions
          rule.validate(dom_cache, rule_result);
        }   
   
        evaluation_result.rule_results.push(rule_result);
        
        var rs = rule_result.getResultSummary();
        
        evaluation_result.result_summary.addPassed(rs.passed);
        evaluation_result.result_summary.addViolations(rs.violations);
        evaluation_result.result_summary.addWarnings(rs.warnings);
        evaluation_result.result_summary.addManualChecks(rs.manual_checks);
        evaluation_result.result_summary.addHidden(rs.hidden);
        
        
      }
      
      // OpenAjax.a11y.logger.debug("Aggregating rule Results: " + rule_result);

    }                 
  } // end rule loop
          
  return evaluation_result;
  
};


/**
 * @method toJSON
 *
 * @memberOf OpenAjax.a11y.Ruleset
 *
 * @desc Creates a JSON representation of the rules in the ruleset 
 *
 * @param  {String}  prefix         - A prefix string typically spaces for formatting output
 * @param  {Number}  rule_category  - Number representing the rule categories to include 
 *
 * @return {String} JSON formatted string representing the ruleset
 */
 
OpenAjax.a11y.Ruleset.prototype.toJSON = function (prefix, rule_category) {
  
  function referencesToJSON(name, refs, last) {
  
    var title = "";
    var url = "";

    var refs_len = refs.length;
    var refs_last = refs_len - 1;
      
    if (refs_len > 0) {
      json += next_prefix + "  \"" + OpenAjax.a11y.util.escapeForJSON(name) + "\" : [\n"; 
      for (var i = 0; i < refs_len; i++) {
        var ref = refs[i];
        
        if (typeof ref === 'string') {
           title = ref;
           url = "";
        }
        else {
           title = ref.title;
           url = ref.url;
        }
        
        if (i === refs_last) json += next_prefix_2 + "{ \"title\" : \"" + OpenAjax.a11y.util.escapeForJSON(title) + "\", \"url\" : \"" + OpenAjax.a11y.util.escapeForJSON(url) + "\"}\n";
        else json += next_prefix_2 + "{ \"title\" : \"" + OpenAjax.a11y.util.escapeForJSON(title) + "\", \"url\" : \"" + OpenAjax.a11y.util.escapeForJSON(url) + "\"},\n";
      }       
      json += next_prefix + "  ]"; 
    }
    else {
      json += next_prefix + "  \"purpose\"    : []";       
    }
    
    if (typeof last === 'undefined' || !last) json += ',\n';
    else json += '\n';
    
  } // end function
    
  var next_prefix = "";
  var next_prefix_2 = "";

  if (typeof prefix !== 'string' || prefix.length === 0) {
    prefix = "";
  }  
  else {
    next_prefix   = prefix + "    ";  
    next_prefix_2 = prefix + "        ";  
  }
  
  var json = "";
  
  json += "{\n";

  json += prefix + "  \"ruleset_id\"          : \"" + this.ruleset_id + "\",\n";
  json += prefix + "  \"ruleset_title\"       : \"" + OpenAjax.a11y.util.escapeForJSON(this.getRulesetTitle()) + "\",\n";
  json += prefix + "  \"ruleset_description\" : \"" + OpenAjax.a11y.util.escapeForJSON(this.getRulesetDescription()) + "\",\n";
  json += prefix + "  \"ruleset_author_name\" : \"" + OpenAjax.a11y.util.escapeForJSON(this.getRulesetAuthor()) + "\",\n";
  json += prefix + "  \"ruleset_author_url\"  : \"" + this.ruleset_author_url + "\",\n";
  json += prefix + "  \"ruleset_updated\"     : \"" + this.ruleset_updated + "\",\n";
  json += prefix + "  \"wcag20_level\"        : "  + this.wcag20_level + ",\n";
  json += prefix + "  \"rec_rules_enabled\"   : "  + this.recommended_rules_enabled + ",\n";


  var rule_mappings = this.rule_mappings;
  var rule_mappings_len = rule_mappings.length;

  if (rule_mappings_len > 0) {
    json += prefix + "  \"rule_mappings\" : {\n";
    

    for (var i = 0; i < rule_mappings_len; i++) {
    
      var rule_mapping = rule_mappings[i];
      var rule = rule_mapping.rule;
      var rule_definition  = rule.getRuleDefinition(rule_mapping.required);

      json += next_prefix + "\"" + rule.rule_id + "\" : {\n"; 
      json += next_prefix + "  \"enabled\"          : "  + rule_mapping.enabled + ",\n"; 
      json += next_prefix + "  \"required\"         : "  + rule_mapping.required + ",\n";
      json += next_prefix + "  \"scope_element\"    : "  + rule.isScopeElement() + ",\n"; 
      json += next_prefix + "  \"nls_scope\"        : \"" + rule.getRuleScope() + "\",\n"; 
      json += next_prefix + "  \"wcag_primary_id\"  : \"" + rule.getPrimarySuccessCriterion() + "\",\n"; 
      json += next_prefix + "  \"wcag_related_ids\" : \"" + rule.getRelatedSuccessCriteria() + "\",\n"; 
      json += next_prefix + "  \"definition\"       : \"" + OpenAjax.a11y.util.escapeForJSON(rr.getRuleDefinition(rule_mapping.required)) + "\",\n"; 
      json += next_prefix + "  \"summary\"          : \"" + OpenAjax.a11y.util.escapeForJSON(rr.getRuleSummary(rule_mapping.required)) + "\",\n"; 
      
      referencesToJSON('purpose', rr.getPurpose());
      referencesToJSON('techniques', rr.getTechniques());
      referencesToJSON('informational_links', rr.getInformationalLinks(), true);

      if (i === last) json += next_prefix + "}\n";
      else json += next_prefix + "},\n";      
    }  // end loop
    
   json += prefix + "  }\n";
   
  }
  else {
    json += prefix + "  \"rule_mappings\" : {}\n";
  }
  
  json += prefix + "}\n";
 
  return json;
    
};


/* ---------------------------------------------------------------- */
/*                       RuleMapping                          */
/* ---------------------------------------------------------------- */

/**
 * @constructor RuleMapping
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc  Contains information about a rule in a ruleset
 *
 * @param  {String}   rule_id   - id of the rule
 * @param  {Boolean}  required  - Boolean indicating if the rule is required or 
 *                                recommended
 * @param  {Boolean}  enabled   - Initial value for the enabled property
 *
 * @property  {Object}   rule      - rule object 
 * @property  {Boolean}  required  - Boolean indicating if the rule is required or 
 *                                   recommended
 * @property  {Boolean}  enabled   - True if the rule is enabled, otherwise false
 * 
 */
 

OpenAjax.a11y.RuleMapping = function (rule_id, required, enabled) {

   this.rule         = null;
   this.required     = required;
   this.enabled      = enabled;

   var r = OpenAjax.a11y.all_rules.getRuleByRuleId(rule_id);
   
   if (r) {
     this.rule = r;
   }
   else {
     OpenAjax.a11y.logger.error("  ** Rule with rule id='" + rule_id + "' does not exist!");   
   }
   
};



/*
 * Copyright 2011 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*                      Logger APIs                                 */ 
/* ---------------------------------------------------------------- */

/**
 * @object logger
 *
 * @memberOf OpenAjax.a11y
 *
 * @desc logger object supports sending messages to the console
 *       This default logger object does nothing other than enumerate
 *       the minimal interface that must be implemented for logging
 *       Use the setLogger function to replace the default object with an
 *       object that supports logging in your development environment
 *       The replacement object may have additional methods defined by 
 *       the host for controlling logging, but the OpenAjax Evaluation 
 *       library will only use these 4 methods.  
 */

OpenAjax.a11y.logger = OpenAjax.a11y.logger || {
  debug: function (message) {},
  info:  function (message) {},
  warn:  function (message) {},
  error: function (message) {}
};

OpenAjax.a11y.setLogger = function (logger) {
   OpenAjax.a11y.logger = logger;
};

/* ---------------------------------------------------------------- */
/*                   OpenAjax High Level APIs                       */ 
/* ---------------------------------------------------------------- */

// basic info about version of ruleset and rules
OpenAjax.a11y.name = "OpenAjax Alliance Accessibility Tools Task Force";
OpenAjax.a11y.version = "2.0.0";
OpenAjax.a11y.baseUri = "http://www.openajax.org/member/wiki/Accessibility";
    

/*
 * OpenAjax registration information 
 */

if (OpenAjax && OpenAjax.hub) {
  OpenAjax.hub.registerLibrary(this.name, this.baseUri, this.version);
}

/**
 * @object locale
 *
 * @memberOf OpenAjax.a11y
 *
 * @type String
 *
 * @default en-us
 *
 * @desc Current locale messages should use for generating text 
 */

OpenAjax.a11y.locale = "en-us";


/**
 * @object all_rules
 *
 * @memberOf OpenAjax.a11y
 *
 * @type Rules
 *
 * @desc Object containing data and methods related to rules
 */

OpenAjax.a11y.all_rules = OpenAjax.a11y.all_rules || new OpenAjax.a11y.Rules();

/**
 * @object cache_nls
 *
 * @memberOf OpenAjax.a11y
 *
 * @type CacheNLS
 *
 * @desc Object containing data and methods for generating human readable text for cache item properties and values
 */

OpenAjax.a11y.cache_nls = OpenAjax.a11y.cache_nls || new OpenAjax.a11y.nls.Cache();

/**
 * @object all_wcag20_nls
 *
 * @memberOf OpenAjax.a11y
 *
 * @type WCAG20NLS
 *
 * @desc Object containing data and methods related to localized versions of WCAG 2.0 requirements 
 */

OpenAjax.a11y.all_wcag20_nls = OpenAjax.a11y.all_wcag20_nls || new OpenAjax.a11y.nls.WCAG20();

/**
 * @function all_rulsets
 *
 * @memberOf OpenAjax.a11y
 *
 * @type Object
 *
 * @desc Object containing data and methods related to rulesets available for evaluation 
 */

OpenAjax.a11y.all_rulesets   = OpenAjax.a11y.all_rulesets   || new OpenAjax.a11y.Rulesets();
/*
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*         Initialize HMTL Report Content, Scripts and CSSS         */
/* ---------------------------------------------------------------- */

/**
 * Initialize css and scripts for report files
 */
 
OpenAjax.a11y.report_css                     = '  <style type="text/css">\n  <!-- Not initialized --> \n  </style>\n';

OpenAjax.a11y.report_element_type_view_js    = '  <script type="text/javascript">\n  <!-- Not initialized --> \n  </script>\n';
OpenAjax.a11y.report_element_type_view_body  = '  <body>\n  <!-- Not initialized --> \n  </body>\n';

OpenAjax.a11y.report_rule_category_view_js   = '  <script type="text/javascript">\n  <!-- Not initialized --> \n  </script>\n';
OpenAjax.a11y.report_rule_category_view_body = '  <body>\n  <!-- Not initialized --> \n  </body>\n';

OpenAjax.a11y.report_rule_summary_view_js    = '  <script type="text/javascript">\n  <!-- Rule Report Summary not initialized --> \n  </script>\n';
OpenAjax.a11y.report_rule_summary_view_body  = '  <body>\n  <!-- Rule Report Summary not initialized --> \n  </body>\n';

OpenAjax.a11y.rule_export_body       = '  <body>\n  <!-- Rule Export HTML code not initialized --> \n  </body>\n';
OpenAjax.a11y.rule_export_js         = '  <script type="text/javascript">\n  <!-- Rule Export Javascript not initialized --> \n  </script>\n';
OpenAjax.a11y.rule_export_css        = '  <style type="text/css">\n  <!-- Rule Export CSS not initialized --> \n  </style>\n';

/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* -------------------------------------------------------------------------------------- */
/* OpenAjax Alliance Cache Properties and Values National Language Support (NLS): English */
/* -------------------------------------------------------------------------------------- */
   

OpenAjax.a11y.cache_nls.addCacheNLSFromJSON('en-us', {

    /*
     * Boolean values 
     */
    boolean_values : {
     true_value  : 'True',
     false_value : 'False'
    }, 

    not_boolean_value : "Not a boolean value",

    yes_no_values : {
     yes_value : 'Yes',
     no_value  : 'No'
    }, 

    required    : 'Required',
    recommended : 'Recommended',
    
    filteredItem  : "filtered element",
    filteredItems : "filtered elements",

    filteredRule  : "filtered rule",
    filteredRules : "filtered rules",
    
    undefined : "undefined",
    empty     : "empty",
    
    message   : "Message",
    
    rules     : "rules",
    rule      : "rule",

    noAction  : "No action needed",

    /*
     * The types of ways a rule can be included in a ruleset
     */
    rule_categories: [
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
      title : 'Audio and Video Rules',
      url   : '',
      desc  : 'The audio and video rules evaluate the object, audio and video elements on a page for text transcripts, captions and audio descriptions.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
      title : 'Data Table Rules',
      url   : '',
      desc  : 'The data table rules include table, thead, tbody, tr, th and td elements ann their attirbutes to make simple and complex data tables accessible.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
      title : 'Form Control Rules',
      url   : '',
      desc  : 'The form control rules include the labeling of form controls and error feedback.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
      title : 'Image Rules',
      url   : '',
      desc  : 'The image rules include the use and describing of img and area elements in a web page.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT,
      title : 'Keyboard Support Rules',
      url   : '',
      desc  : ''
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.LINKS,
      title : 'Link Rules',
      url   : '',
      desc  : 'The link rules include the use of the a and area elements for accessible links on a web page.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
      title : 'Structure and Navigation Rules',
      url   : '',
      desc  : 'The rules related to titling, headers, ARIA landmarks, lists, abbreviations and language.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER,
      title : 'Styling, Color and Reading Order Rules',
      url   : '',
      desc  : 'The color and styling rules include color contrast of text and the use of color to display information on a web page.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
      title : 'Widget and Script Rules',
      url   : '',
      desc  : 'The widget rules evaluate the use of event handlers on elements other than form controls for the creation of interactive elements.  Interactive elements created this way need to support the keyboard and have ARIA markup to describe the widgets and their proeprties and states.'
    },
    // Composite rule categories
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.ALL,
      title : 'All Rules',
      url   : '',
      desc  : 'The all rule category includes all the rules in the ruleset and provides a way to sort and compare the results of all the rules.'
    },
    {
      id    : OpenAjax.a11y.RULE_CATEGORIES.EVALUATION_RESULTS,
      title : 'All Evaluation Results',
      url   : '',
      desc  : 'Evaluation results include all rule results and do not filter out any node results.'
    }
    ],

    /*
     * Relative implementation priorities of complying to rule requirements
     */
    priorities: ['Undefined', 'First Priority', 'Second Priority', 'Third Priority'],

    /*
     * Types of rule references to a requirement
     */
    references: ['Unknown', 'Requirement', 'Coding Technique', 'Manual Evaluation', 'Best Practice', 'Authoring Technique', 'Other'],

    /*
     * Abbreviation for the types of rule references to a requirement
     */
    reference_abbreviations: ['U', 'R', 'C', 'ME', 'BP', 'A', 'O'],

    /*
     * Media constant values
     */
    reference_media_contants: ['Undefined', 'No', 'Maybe', 'Yes'],
    
    missing_label : {
      label : "no label",
      style : "missing_label"
    },  
    
    empty_alt_text : {
      label : "empty alt",
      style : "empty_alt"
    },

    missing_alt : {
      label : "missing alt attribute",
      style : "empty_alt"
    },
    
    invalid_attribute : {
      label : "invalid attribute",
      value : "",
      style : "invalid"
    },

    invalid_value : {
      label : "",
      value : " (invalid)",
      style : "invalid"
    },

    /**
     * Result types for the evaluation of a rule 
     */
    rule_types: [ { label       : 'None', 
                    abbrev      : 'none', 
                    description : 'No rule results',
                    style       : 'none'
                  },
                  { label       : 'Not Applicable', 
                    abbrev      : 'na', 
                    description : 'Rule did not apply',
                    style       : ''
                  },
                  { label       : 'Hidden', 
                    abbrev      : 'H', 
                    description : 'Element is hidden from users and was not evaluted for accessibility',
                    style       : 'hidden'
                  },
                  { label       : 'Passed', 
                    abbrev      : 'P', 
                    description : 'Passed a required or recommended rule',
                    style       : 'passed'
                  },
                  { label       : 'Manual Check', 
                    abbrev      : 'MC', 
                    description : 'The rule requires human inspection and judgement to determine if the requirements of the rule has been met',
                    style       : 'manual_check'
                  },
                  { label      : 'Violation', 
                    abbrev      : 'V', 
                    description : 'Failure of a required rule',
                    style       : 'violation'
                  },
                  { label       : 'Warning', 
                    abbrev      : 'W', 
                    description : 'Failure of a recommended rule',
                    style       : 'warning'
                  },
                  { label       : 'Page', 
                    abbrev      : 'pg', 
                    description : 'Element related to a page level rule',
                    style       : 'page'
                  }
                  ],  
                  
    /*
     * Status of a rule for evaluating a requirement
     */
    status: ['Undefined', 'Proposed', 'Accepted', 'Deprecated'],

    resource_properties : {

    /*
     * DOMElement object properties
     */

      'document_order' : {
        label       : 'Order',
        description : 'The ordinal position of the item in the list',
        style       : 'doc_order'
      },
      'tag_name' : {
        label       : 'Tag Name',
        description : 'Tag (or element) name of the item',
        style       : 'element'
      },
      'id' : { 
        label       : 'id',
        description :  'Value of the id attribute'
      },
      'id_unique'   : { 
        label       : 'ID unique',
        description :  'Information about the id attribute value',
        values      : ['Undefined value', 'Not defined', 'Unique', 'Not unique'], 
        style       : ['','','','warning']
      },  
      'character_count' : { 
        label       : 'Text Count',
        description :  'Number of characters in the text content of this tag'
      },
      'class_name'  : { 
        label       : 'class',
        description :  'Value of the HTML class attribute'
      },
      'role'        : { 
        label       : 'role',
        description :  'Can be used to redefine the role of an element into a landmark or widget'
      },
      'alt'         : { 
        label       : 'alt',
        description :  'Value of the HTML alt attribute'
      },
      'alt_for_comparison' : { 
        label       : 'Normlized Alt',
        description : 'Normalized version of the alt text content used for comparison'
      },
      'has_alt_attribute' : { 
        label       : 'Alt Defined',
        description : 'True if the alt attribute was defined in markup'
      },
      'alt_length' : { 
        label       : 'Alt text length',
        description : 'The length of the text in the alt text attribute'
      },
      'title'       : { 
        label       : 'title',
        description : 'Value of the HTML title attribute'
      },
      'aria_describedby' : { 
        label       : 'aria-describedby',
        description : 'aria-describedby can be used to provide additional information about an element to AT users'
      },
      'aria_hidden' : { 
        label       : 'aria-hidden',
        description :  'aria-hidden can be used to hide information from assistive technologies that is visible graphically'
      },
      'aria_label'  : { 
        label       : 'aria-label',
        description :  'aria-label can be used to label form controls and widgets'
      },
      'aria_labelledby' : { 
        label       : 'aria-labelledby',
        description :  'aria-labelledby can be used to label form controls and widgets'
      },
      'xpath'       : { 
        label       : 'XPath',
        description : 'XPath information used for identifying the location of the element in the DOM'
      },
      'has_aria_describedby' : { 
        label       : 'Description',
        description : 'Description defined using the aria-describedby attribute'
      },
      'calculated_aria_description' : { 
        label       : 'Calculated Description',
        description : 'Calculated text content of a description defined using the aria-describedby attribute'
      },
      'for_id'  : { 
        label       : 'for',
        description : 'Value of the for attribute of a label element'
      },
      'parent_landmark_role'  : { 
        label       : 'Parent landmark role',
        description : 'Role of the landmark that contains this content'
      },
      'parent_landmark'  : { 
        label       : 'Containing landmark element',
        description : 'Landmark element that contains this content'
      },
      'is_widget'  : { 
        label       : 'ARIA widget role',
        description : 'If element is part of an aria widget'
      },
      'is_landmark'  : { 
        label       : 'ARIA landmark role',
        description : 'If element is part of an aria widget'
      },


    /*
     * Calculated values based on CSS properties
     */

      'graphical' : {
        label       : 'Graphical Visibility',
        description : 'Can the item be seen visually',
        values      : ['Undefined value', 'Unknown', 'Hidden', 'Visible']
      }, 
      'is_large_font' : { 
        label       : 'Large Font',
        description : 'Boolean value used in WCAG 2.0 evaluation of color contrast ratio'
      }, 
      'is_visible_to_at' : { 
        label       : 'AT Visible',
        description : 'Is the element and its content visible to Assistive Technology',
        values : ['undefined', 'unknown', 'hidden', 'visible']
      }, 
      'is_visible_onscreen' : { 
        label       : 'Screen Visible',
        description : 'Is the element and its content visible on screen',
        values : ['undefined', 'unknown', 'hidden', 'visible']
      }, 


    /*
     * Run time CSS style properties
     */

      'display'              : {
        label       : 'display',
        description :  'The value of the CSS display property'
      }, 
      'visibility'           : {
        label       : 'visibility',
        description :  'The value of the CSS visibility property'
      },        
      'color'                : {
        label       : 'color',
        description :  'The value of the CSS color property'
      },               
      'background_color' : {
        label       : 'background-color',
        description :  'The value of the CSS background-color property'
      },
      'background_image' : {
        label       : 'background-image',
        description :  'The value of the CSS background-image property'
      },
      'font_family'          : {
        label       : 'font-family',
        description :  'The value of the CSS font-family property'
      },
      'font_size'            : {
        label       : 'font-size',
        description :  'The value of the CSS font-size property'
      },
      'font_weight'          : {
        label       : 'font-weight',
        description :  'The value of the CSS font-weight property'
      },
      'position'             : {
        label       : 'position',
        description :  'The value of the CSS position property'
      },
      'left'                 :  {
        label       : 'left',
        description :  'The value of the CSS left property'
      },
      'top'                  : {
        label       : 'top',
        description :  'The value of the CSS top property'
      },

    /*
     * Abbreviation Cache object properties
     */
      'abbreviation_text' : {
        label       : 'Abbreviation',
        description :  'The text content of an ABBR or ACROYMN element'
      },

    /*
     * Image Cache object attributes
     */
      'source' : {
        label       : 'src',
        description : 'Value of the src attribute'
      },


    /*
     * Control Cache object attributes
     */
      'computed_label' : {
        label       : 'Label (computed)',
        description : 'The label communicated to assistive technologies for identifying the form control.'
      },     
      'computed_label_source' : {
        label       : 'Labeling Technique',
        description : 'The technique for defining the label',
        values      : ['unkown', 'none', 'label by reference', 'label encapsulation', 'title attribute', 'value attribute', 'alt attribute', 'button type', 'child text content', 'aria labelledby', 'aria label']
      },     
      'num_main_landmarks' : {
        label       : 'Main landmarks',
        description : 'Number of main landmarks'
      },     
      'num_visible_main_landmarks' : {
        label       : 'Visibile main landmarks',
        description : 'Number of visible main landmarks'
      },     
      'fieldset_element' : {
        label       : 'Fieldset/Legend',
        description : 'Content of the fieldset legend element'
      },     
      'legend_count' : {
        label       : 'Legend Count',
        description : 'Number of legend elements contained in a fieldset element'
      },     
      'accessible_name' : {
        label       : 'Name',
        description : 'The name of a widget used by assistive technologies to identify the widget.'
      },     
      'accessible_name_for_comparison' : {
        label       : 'Name (normalized)',
        description : 'Normailzed name of a widget for use in comparisons.'
      },     
      'accessible_description' : {
        label       : 'Description',
        description : 'The description used by assistive technologies to provide additional information about a form control, widget, link, image or other element.'
      },     
      'accessible_description_for_comparison' : {
        label       : 'Description (normalized)',
        description : 'Normalized description used for comparisons.'
      },     
    /*
     * Link Cache object attributes
     */
      'is_url' : {
        label       : 'is a url',
        description : 'Boolean value indicating if a href contains a URL'
      },
      'is_target' : {
        label       : 'is a target',
        description : 'Boolean value indicating if the link can be a target'
      },
      'link_type' : {
        label       : 'Type of link',
        description : 'Type of link',
        values      : ['empty', 'other', 'internal link', 'link', 'secure link', 'ftp', 'secure ftps', 'file', 'javascript', 'mail to', 'target only']
      },
      'accessible_name_source' : {
        label       : 'Name from',
        description : 'The technique for defining the accessible name of a link',
        values      : ['unkown', 'none', 'label by reference', 'label encapsulation', 'title attribute', 'value attribute', 'alt attribute', 'button type', 'child text content', 'aria labelledby', 'aria label']
      },     
      'is_broken' : {
        label       : 'Link broken',
        description : 'Tests to see if the link is broken, valid or has an error',
        values      : ['unkown',  'broken', 'vaild', 'not tested', 'error']
      },
      'name_attribute' : {
        label       : 'Name attribute',
        description : 'Value of the name attribute'
      },
      'target' : {
        label       : 'target',
        description : 'Value of the target attribute'
      },

    /*
     * Media Cache object properties
     */

      'is_live'  : {
        label       : 'Live',
        description :  'Is the media live video or audio',
        values      :  ['undefined', 'No', 'Maybe, manual verification required', 'Yes']
        
      },

      'is_video'  : {
        label       : 'Video',
        description :  'Does the media object contain video',
        values      :  ['undefined', 'No', 'Maybe, manual verification required', 'Yes']
        
      },

      'is_audio'  : {
        label       : 'Audio',
        description :  'Does the media object contain audio',
        values      :  ['undefined', 'No', 'Maybe, manual verification required', 'Yes']
      },

      'has_caption'  : {
        label       : 'Caption',
        description :  'Does the media object have captions',
        values      :  ['undefined', 'No', 'Maybe, manual verification required', 'Yes']
      },

      'has_text_alternative' : {
        label       : 'Text Equivalent',
        description :  'Does the media object have a text equivalent',
        values      :  ['undefined', 'No', 'Maybe, manual verification required', 'Yes']
      },
      
      'has_audio_description' : {
        label       : 'Audio Equivalent',
        description :  'Does the media object have a audio equivalent',
        values      :  ['undefined', 'No', 'Maybe, manual verification required', 'Yes']
      },
      
    /*
     * Name Cache object properties
     */

      'name'  : {
        label       : 'Name',
        description : 'Text content of the element'
      },
      'name_for_comparison' : {
        label       : 'Normalized name',
        description : 'Text content of the element normalized for use in comparisons'
      },
      'name_from_text_nodes' : {
        label       : 'Name from text',
        description : 'Text content of the element that comes from text dom nodes'
      },
      'name_from_image_alt' : {
        label       : 'Name from alt',
        description : 'Text content of the element that comes from alt text of images'
      },
      'image_count' : {
        label       : 'Image count',
        description : 'Number of images contained in the element'
      },
      'text_only_from_image' : {
        label       : 'Image only',
        description : 'Does the text content only come from images'
      },

    /*
     * List Cache object properties
     */

      'list_type'  : {
        label       : 'List Type',
        values      : ['Undefined', 'Container element', 'Item element', 'Landmark element'],        
        description : 'Type of list cache element'
      },
      
    /*
     * Table Cache object properties
     */

      'row'                      : {
        label       : 'Row',
        description :  'The row of the cell in a table'
      },
      'column'                   : {
        label       : 'Column',
        description :  'The column of the cell in a table'
      },
      'max_row'                      : {
        label       : 'Rows',
        description :  'Number of rows in a table'
      },
      'max_column'                   : {
        label       : 'Columns',
        description :  'Number of columns in a table'
      },
      'number_of_header_ids' : {
        label       : 'Header ID Num',
        description :  'Number of ids in a headers attribute'
      },              
      'text_content'             : {
        label       : 'Text',
        description :  'Text content of a table cell'
      },              
      'header_content'             : {
        label       : 'Header',
        description :  'Text content of header cells associated with the data cell'
      },
      'header_source'  : {
        label       : 'Header Source',
        values      : ["Undefined", "none", "headers attribute", "Column/Row header cells"],        
        description : 'How header cells were calculated for the table'
      },              
      'text_normalized'    : {
        label       : 'Text',
        description : 'Text content of a element'
      },              
      'effective_caption'        : {
        label       : 'Effective Caption',
        description : 'Effective caption is the text content of a caption element or ARIA labeling'
      },
      'effective_summary'        : {
        label       : 'Effective Summary',
        description : 'Effective summary is the text content of a summary attribute or an aria-describedby attribute'
      },
      'is_data_table'            : {
        label       : 'Data Table',
        description :  'True if a table has been identified as a data table, otherwise false'
      },
      'is_complex_data_table'            : {
        label       : 'Complex Data Table',
        description :  'True if a table has been identified as a complex data table, otherwise false'
      },
      'first_row_th_count'       : {
        label       : 'Header Count',
        description :  'The number of header cells in the first row or column of a data table'
      },
      'first_row_cell_count' : {
        label       : 'Cell Count',
        description :  'The number of none empty cells in the first row or column of a data table'
      },     
      'scope'               : {
        label       : 'scope',
        description :  'The value of the scope attribute of a table cell'
      },
      'headers'             : {
        label       : 'headers',
        description :  'The value of the headers attribute of a table cell'
      },
      'row_span'            : {
        label       : 'rowspan',
        description :  'The value of the rowspan attribute of a table cell'
      },
      'column_span'         : {
        label       : 'colspan',
        description :  'The value of the colspan attribute of a table cell'
      },
      'nesting_level'         : {
        label       : 'Nesting',
        description :  'The level of nesting of a layout table in other tables that are wider than 1 column and are not data tables'
      },
      'table_type'  : {
        label       : 'Table Type',
        values      : ["Undefined", "Table","Caption","Table Head","Table Body", "Row", "Header Cell", "Data Cell"],        
        description : 'Effective type of table header'
      }
      
    }
  }
);
/**
 * Copyright 2011 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* --------------------------------------------------------------------------- */
/* OpenAjax Alliance WCAG 2.0 National Language Support (NLS): English         */
/* --------------------------------------------------------------------------- */

OpenAjax.a11y.all_wcag20_nls.addNLS('en-us', {
  abbreviation : 'WCAG 2.0',
  title        : 'Web Content Accessibility Guidelines 2.0',
  url          : 'http://www.w3c.org/TR/WCAG20',

  level : "Level ",

  levels : ['Undefined', 'AAA','AA','', 'A'],

  evaluation_levels : ['Undefined', 'AAA','AA','AA and AAA', 'A',  'A and AAA', 'A nd AA', 'A, AA and AAA'],

  principles : {
    //
    // Principle 1: Perceivable
    //
    '1' : {
      id          : OpenAjax.a11y.WCAG20_PRINCIPLE.P_1,
      title       : 'Principle 1: Perceivable',
      description : 'Information and user interface components must be presentable to users in ways they can perceive.', 
      url_spec    : 'http://www.w3.org/TR/WCAG20/#perceivable',
      guidelines : {
        //
        // Guideline 1.1 Text Alternatives
        //
        '1.1' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_1_1,  
          title       : 'Guideline 1.1 Text Alternatives',
          description : 'Provide text alternatives for any non-text content so that it can be changed into other forms people need, such as large print, braille, speech, symbols or simpler language.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#text-equiv',
          success_criteria : {
            //
            // Success Criterion 1.1.1 Non-text Content: All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below. 
            //
            '1.1.1' : {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_1_1,          
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '1.1.1 Non-text Content',
              description    : 'All non-text content that is presented to the user has a text alternative that serves the equivalent purpose, except for the situations listed below.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#text-equiv',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-text-equiv-all',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/text-equiv-all.html',
              references     : []
            }
          }
        },  
        //
        // Guideline 1.2 Time-based Media
        //
        '1.2' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_1_2,  
          title       : 'Guideline 1.2 Time-based Media',
          description : 'Provide alternatives for time-based media.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv',
          success_criteria : {
            //
            // Success Criterion 1.2.1 Audio-only and Video-only (Prerecorded)
            //
            '1.2.1': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_1,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.A,
              title       : '1.2.1 Audio-only and Video-only (Prerecorded)',
              description : 'For prerecorded audio-only and prerecorded video-only media, the following are true, except when the audio or video is a media alternative for text and is clearly labeled as such: (1) Prerecorded Audio-only: An alternative for time-based media is provided that presents equivalent information for prerecorded audio-only content. (2) Prerecorded Video-only: Either an alternative for time-based media or an audio track is provided that presents equivalent information for prerecorded video-only content.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-av-only-alt',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-av-only-alt',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-av-only-alt.html',
              references     : []
            },
            //
            // Success Criterion 1.2.2 Captions (Prerecorded)
            //
           '1.2.2': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_2,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.A,
              title       : '1.2.2 Captions (Prerecorded)',
              description : 'Captions are provided for all prerecorded audio content in synchronized media, except when the media is a media alternative for text and is clearly labeled as such.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-captions',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-captions',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-captions.html',
              references     : []
            },
            //
            // Success Criteria 1.2.3 Audio Description or Media Alternative (Prerecorded)
            //
            '1.2.3': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_3,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.A,
              title       : '1.2.3 Audio Description or Media Alternative (Prerecorded)',
              description : 'An alternative for time-based media or audio description of the prerecorded video content is provided for synchronized media, except when the media is a media alternative for text and is clearly labeled as such.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-audio-desc',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-audio-desc',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-audio-desc.html',
              references     : []
            },
            //
            // Success Criteria 1.2.4 Captions (Live)
            //
            '1.2.4': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_4,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title       : '1.2.4 Captions (Live)',
              description : 'Captions are provided for all live audio content in synchronized media. ',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-real-time-captions',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-real-time-captions',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-real-time-captions.html',
              references     : []
            },
            //
            // Success Criteria 1.2.5 Audio Description (Prerecorded)
            //
            '1.2.5': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_5,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title       : '1.2.5 Audio Description (Prerecorded)',
              description : 'Audio description is provided for all prerecorded video content in synchronized media.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-audio-desc-only',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-audio-desc-only',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-audio-desc-only.html',
              references     : []
            },
            //
            // Success Criteria 1.2.6 Sign Language (Prerecorded)
            //
            '1.2.6': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_6,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title       : '1.2.6 Sign Language (Prerecorded)',
              description : 'Sign language interpretation is provided for all prerecorded audio content in synchronized media.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-sign',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-sign',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-sign.html',
              references     : []
            },
            //
            // Success Criteria 1.2.7 Extended Audio Description (Prerecorded)
            //
            '1.2.7': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_7,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title       : '1.2.7 Extended Audio Description (Prerecorded)',
              description : 'Where pauses in foreground audio are insufficient to allow audio descriptions to convey the sense of the video, extended audio description is provided for all prerecorded video content in synchronized media.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-extended-ad',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-extended-ad',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-extended-ad.html',
              references     : []
            },
            //
            // Success Criteria 1.2.8 Media Alternative (Prerecorded)
            //
            '1.2.8': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_8,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title       : '1.2.8 Media Alternative (Prerecorded)',
              description : 'An alternative for time-based media is provided for all prerecorded synchronized media and for all prerecorded video-only media.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-text-doc',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-text-doc',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-text-doc.html',
              references     : []
            },
            //
            // Success Criteria 1.2.9 Audio-only (Live)
            //
            '1.2.9': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_2_9,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title       : '1.2.9 Audio-only (Live)',
              description : 'An alternative for time-based media that presents equivalent information for live audio-only content is provided. ',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#media-equiv-live-audio-only',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-media-equiv-live-audio-only',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/media-equiv-live-audio-only.html',
              references     : []
            }
          }
        },
        //
        // Guideline 1.3 Adaptable
        //
        '1.3' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_1_3,  
          title       : 'Guideline 1.3 Adaptable',
          description : 'Create content that can be presented in different ways (for example simpler layout) without losing information or structure.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#content-structure-separation',
          success_criteria : {
            //
            // Success Criteria 1.3.1 Info and Relationships
            //
            '1.3.1': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_3_1,          
              level       : OpenAjax.a11y.WCAG20_LEVEL.A,
              title       : '1.3.1 Info and Relationships',
              description : 'Information, structure, and relationships conveyed through presentation can be programmatically determined or are available in text.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#content-structure-separation-programmatic',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-content-structure-separation-programmatic',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-programmatic.html',
              references     : []
            },
            //
            // Success Criteria 1.3.2 Meaningful Sequence
            //
            '1.3.2': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_3_2,
              level       : OpenAjax.a11y.WCAG20_LEVEL.A,
              title       : '1.3.2 Meaningful Sequence',
              description : 'When the sequence in which content is presented affects its meaning, a correct reading sequence can be programmatically determined.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#content-structure-separation-sequence',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-content-structure-separation-sequence',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-sequence.html',
              references     : []
            },
            //
            // Success Criteria 1.3.3 Sensory Characteristics
            //
            '1.3.3': {
              id          : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_3_3,
              level       : OpenAjax.a11y.WCAG20_LEVEL.A,
              title       : '1.3.3 Sensory Characteristics',
              description : 'Instructions provided for understanding and operating content do not rely solely on sensory characteristics of components such as shape, size, visual location, orientation, or sound.',
              url_spec    : 'http://www.w3.org/TR/WCAG20/#content-structure-separation-understanding',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-content-structure-separation-understanding',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-understanding.html',
              references     : []
            }
          }
        },
        //
        // Guideline 1.4 Distinguishable
        //
        '1.4' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_1_4,          
          title       : 'Guideline 1.4 Distinguishable',
          description : 'Make it easier for users to see and hear content including separating foreground from background.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast',
          success_criteria : {
            //
            // Success Criteria 1.4.1 Use of Color
            //
            '1.4.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '1.4.1 Use of Color',
              description    : 'Color is not used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-without-color',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-without-color',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-without-color.html',
              references     : []
            },
            //
            // Success Criteria 1.4.2 Audio Control
            //
            '1.4.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '1.4.2 Audio Control',
              description    : 'If any audio on a Web page plays automatically for more than 3 seconds, either a mechanism is available to pause or stop the audio, or a mechanism is available to control audio volume independently from the overall system volume level.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-dis-audio',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-dis-audio',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-dis-audio.html',
              references     : []
            },
            //
            // Success Criteria 1.4.3 Contrast (Minimum)
            //
            '1.4.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '1.4.3 Contrast (Minimum)',
              description    : 'The visual presentation of text and images of text has a contrast ratio of at least 4.5:1, except for the following: \n(1) Large Text: Large-scale text and images of large-scale text have a contrast ratio of at least 3:1;\n(2) Incidental: Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement.\n(3) Logotypes: Text that is part of a logo or brand name has no minimum contrast requirement.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-contrast',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html',
              references     : []
            },
            //
            // Success Criteria 1.4.4 Resize text
            //
            '1.4.4': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_4,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '1.4.4 Resize text',
              description    : 'Except for captions and images of text, text can be resized without assistive technology up to 200 percent without loss of content or functionality.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-scale',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-scale',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-scale.html',
              references     : []
            },
            //
            // Success Criteria 1.4.5 Images of Text
            //
            '1.4.5': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_5,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '1.4.5 Images of Text',
              description    : 'If the technologies being used can achieve the visual presentation, text is used to convey information rather than images of text except for the following: (1) Customizable: The image of text can be visually customized to the user\'s requirements; (2) Essential: A particular presentation of text is essential to the information being conveyed.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-text-presentation',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-text-presentation',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-text-presentation.html',
              references     : []
            },
            //
            // Success Criteria 1.4.6 Contrast (Enhanced)
            //
            '1.4.6': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_6,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '1.4.6 Contrast (Enhanced)',
              description    : 'The visual presentation of text and images of text has a contrast ratio of at least 7:1, except for the following: (1) Large Text: Large-scale text and images of large-scale text have a contrast ratio of at least 4.5:1; (2) Incidental: Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement. (3) Logotypes: Text that is part of a logo or brand name has no minimum contrast requirement.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast7',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast7',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html',
              references     : []
            },
            //
            // Success Criteria 1.4.7 Low or No Background Audio
            //
            '1.4.7': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_7,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '1.4.7 Low or No Background Audio',
              description    : 'For prerecorded audio-only content that (1) contains primarily speech in the foreground, (2) is not an audio CAPTCHA or audio logo, and (3) is not vocalization intended to be primarily musical expression such as singing or rapping, at least one of the following is true: (4a) No Background: The audio does not contain background sounds. (4b) Turn Off: The background sounds can be turned off. (4c) 20 dB: The background sounds are at least 20 decibels lower than the foreground speech content, with the exception of occasional sounds that last for only one or two seconds.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-noaudio',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-noaudio',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-noaudio.html',
              references     : []
            },
            //
            // Success Criteria 1.4.8 Visual Presentation
            //
            '1.4.8': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_8,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '1.4.8 Visual Presentation',
              description    : 'For the visual presentation of blocks of text, a mechanism is available to achieve the following: (1) Foreground and background colors can be selected by the user; (2) Width is no more than 80 characters or glyphs (40 if CJK); (3) Text is not justified (aligned to both the left and the right margins); (4) Line spacing (leading) is at least space-and-a-half within paragraphs, and paragraph spacing is at least 1.5 times larger than the line spacing; (5) Text can be resized without assistive technology up to 200 percent in a way that does not require the user to scroll horizontally to read a line of text on a full-screen window.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-visual-presentation',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-visual-presentation',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-visual-presentation.html',
              references     : []
            },
            //
            // Success Criteria 1.4.9 Images of Text (No Exception)
            //
            '1.4.9': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_1_4_9,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '1.4.9 Images of Text (No Exception)',
              description    : 'Images of text are only used for pure decoration or where a particular presentation of text is essential to the information being conveyed.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-text-images',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast-text-images',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-text-images.html',
              references     : []
            }
          }
        }
      }
    },  
    //
    // Principle 2: Operable
    //
    '2' : {
      id          : OpenAjax.a11y.WCAG20_PRINCIPLE.P_2,
      title       : 'Principle 2: Operable',
      description : 'User interface components and navigation must be operable.', 
      url_spec    : 'http://www.w3.org/TR/WCAG20/#operable',
      guidelines : {
        //
        // Guideline 2.1 Keyboard Accessible
        //
        '2.1' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_2_1,  
          title       : 'Guideline 2.1 Keyboard Accessible',
          description : 'Make all functionality available from a keyboard.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#keyboard-operation',
          success_criteria : {
            //
            // Success Criterion 2.1.1 Keyboard
            //
            '2.1.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_1_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.1.1 Keyboard',
              description    : 'All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes, except where the underlying function requires input that depends on the path of the user\'s movement and not just the endpoints.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#keyboard-operation-keyboard-operable',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-keyboard-operation-keyboard-operable',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-keyboard-operable.html',
              references     : []
            },
            //
            // Success Criterion 2.1.2 No Keyboard Trap
            //
            '2.1.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_1_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.1.2 No Keyboard Trap',
              description    : 'If keyboard focus can be moved to a component of the page using a keyboard interface, then focus can be moved away from that component using only a keyboard interface, and, if it requires more than unmodified arrow or tab keys or other standard exit methods, the user is advised of the method for moving focus away.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#keyboard-operation-trapping',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-keyboard-operation-trapping',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-trapping.html',
              references     : []
            },
            //
            // Success Criterion 2.1.3 Keyboard (No Exception)
            //
            '2.1.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_1_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.1.3 Keyboard (No Exception)',
              description    : 'All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#keyboard-operation-all-funcs',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-keyboard-operation-all-funcs',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/keyboard-operation-all-funcs.html',
              references     : []
            }
          }
        },
        //
        // Guideline 2.2 Enough Time
        //
        '2.2' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_2_2,  
          title       : 'Guideline 2.2 Enough Time',
          description : 'Provide users enough time to read and use content.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#time-limits',
          success_criteria : {
            //
            // Success Criterion 2.2.1 Timing Adjustable
            //
            '2.2.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_2_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.2.1 Timing Adjustable',
              description    : 'For each time limit that is set by the content, at least one of the following is true: (1) Turn off: The user is allowed to turn off the time limit before encountering it; or (2) Adjust: The user is allowed to adjust the time limit before encountering it over a wide range that is at least ten times the length of the default setting; or (3) Extend: The user is warned before time expires and given at least 20 seconds to extend the time limit with a simple action (for example, "press the space bar"), and the user is allowed to extend the time limit at least ten times; or (4) Real-time Exception: The time limit is a required part of a real-time event (for example, an auction), and no alternative to the time limit is possible; or (5) Essential Exception: The time limit is essential and extending it would invalidate the activity; or (6) 20 Hour Exception: The time limit is longer than 20 hours.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#time-limits-required-behaviors',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-required-behaviors',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-required-behaviors.html',
              references     : []
            },
            //
            // Success Criteria 2.2.2 Pause, Stop, Hide
            //
            '2.2.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_2_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.2.2 Pause, Stop, Hide',
              description    : 'For moving, blinking, scrolling, or auto-updating information, all of the following are true: Moving, blinking, scrolling: For any moving, blinking or scrolling information that (1) starts automatically, (2) lasts more than five seconds, and (3) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it unless the movement, blinking, or scrolling is part of an activity where it is essential; and Auto-updating: For any auto-updating information that (1) starts automatically and (2) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it or to control the frequency of the update unless the auto-updating is part of an activity where it is essential.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#time-limits-pause',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-pause',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-pause.html',
              references     : []
            },
            //
            // Success Criteria 2.2.3 No Timing
            //
            '2.2.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_2_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.2.3 No Timing',
              description    : 'Timing is not an essential part of the event or activity presented by the content, except for non-interactive synchronized media and real-time events.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#time-limits-no-exceptions',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-no-exceptions',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-no-exceptions.html',
              references     : []
            },
            //
            // Success Criteria 2.2.4 Interruptions
            //
            '2.2.4': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_2_4,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.2.4 Interruptions',
              description    : 'Interruptions can be postponed or suppressed by the user, except interruptions involving an emergency.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#time-limits-postponed',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-postponed',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-postponed.html',
              references     : []
            },
            //
            // Success Criteria 2.2.5 Re-authenticating
            //
            '2.2.5': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_2_5,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.2.5 Re-authenticating',
              description    : 'When an authenticated session expires, the user can continue the activity without loss of data after re-authenticating.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#time-limits-server-timeout',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-time-limits-server-timeout',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/time-limits-server-timeout.html',
              references     : []
            }          
          }
        },
        //
        // Guideline 2.3 Seizures
        //
        '2.3' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_2_3,  
          title       : 'Guideline 2.3 Seizures',
          description : 'Do not design content in a way that is known to cause seizures.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#seizure',
          success_criteria : {
            //
            // Success Criteria 2.3.1 Three Flashes or Below Threshold
            //
            '2.3.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_3_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.3.1 Three Flashes or Below Threshold',
              description    : 'Web pages do not contain anything that flashes more than three times in any one second period, or the flash is below the general flash and red flash thresholds.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#seizure-does-not-violate',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-seizure-does-not-violate',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/seizure-does-not-violate.html',
              references     : []
            },
            //
            // Success Criteria 2.3.2 Three Flashes
            //
            '2.3.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_3_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.3.2 Three Flashes',
              description    : 'Web pages do not contain anything that flashes more than three times in any one second period.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#seizure-three-times',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-seizure-three-times',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/seizure-three-times.html',
              references     : []
            }
          }
        },
        //
        // Guideline 2.4 Navigable
        //
        '2.4' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_2_4,  
          title       : 'Guideline 2.4 Navigable',
          description : 'Provide ways to help users navigate, find content, and determine where they are.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms',
          success_criteria : {
            //
            // Success Criteria 2.4.1 Bypass Blocks
            //
            '2.4.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.4.1 Bypass Blocks',
              description    : 'A mechanism is available to bypass blocks of content that are repeated on multiple Web pages.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-skip',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-skip',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-skip.html',
              references     : []
            },
            //
            // Success Criteria 2.4.2 Page Titled
            //
            '2.4.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.4.2 Page Titled',
              description    : 'Web pages have titles that describe topic or purpose.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-title',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-title',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-title.html',
              references     : []
            },
            //
            // Success Criteria 2.4.3 Focus Order
            //
            '2.4.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.4.3 Focus Order',
              description    : 'If a Web page can be navigated sequentially and the navigation sequences affect meaning or operation, focusable components receive focus in an order that preserves meaning and operability.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-order',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-focus-order',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-order.html',
              references     : []
            },
            //
            // Success Criteria 2.4.4 Link Purpose (In Context)
            //
            '2.4.4': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_4,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '2.4.4 Link Purpose (In Context)',
              description    : 'The purpose of each link can be determined from the link text alone or from the link text together with its programmatically determined link context, except where the purpose of the link would be ambiguous to users in general.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-refs',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-refs',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-refs.html',
              references     : []
            },
            //
            // Success Criteria 2.4.5 Multiple Ways
            //
            '2.4.5': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_5,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '2.4.5 Multiple Ways',
              description    : 'More than one way is available to locate a Web page within a set of Web pages except where the Web Page is the result of, or a step in, a process.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-mult-loc',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-mult-loc',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-mult-loc.html',
              references     : []
            },
            //
            // Success Criteria 2.4.6 Headings and Labels
            //
            '2.4.6': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_6,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '2.4.6 Headings and Labels',
              description    : 'Headings and labels describe topic or purpose.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-descriptive',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-descriptive',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-descriptive.html',
              references     : []
            },
            //
            // Success Criteria 2.4.7 Focus Visible
            //
            '2.4.7': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_7,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '2.4.7 Focus Visible',
              description    : 'Any keyboard operable user interface has a mode of operation where the keyboard focus indicator is visible. ',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-focus-visible',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-focus-visible',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-focus-visible.html',
              references     : []
            },
            //
            // Success Criteria 2.4.8 Location
            //
            '2.4.8': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_8,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.4.8 Location',
              description    : 'Information about the user\'s location within a set of Web pages is available.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-location',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-location',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-location.html',
              references     : []
            },
            //
            // Success Criteria 2.4.9 Link Purpose (Link Only)
            //
            '2.4.9': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_9,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.4.9 Link Purpose (Link Only)',
              description    : 'A mechanism is available to allow the purpose of each link to be identified from link text alone, except where the purpose of the link would be ambiguous to users in general.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-link',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-link',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-link.html',
              references     : []
            },
            //
            // Success Criteria 2.4.10 Section Headings
            //
            '2.4.10': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_2_4_10,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '2.4.10 Section Headings',
              description    : 'Section headings are used to organize the content.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#navigation-mechanisms-headings',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-navigation-mechanisms-headings',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/navigation-mechanisms-headings.html',
              references     : []
            }
          }
        }
      }
    },  
    //
    // Principle 3: Understandable
    //
    '3' : {
      id          : OpenAjax.a11y.WCAG20_PRINCIPLE.P_3,
      title       : 'Principle 3: Understandable',
      description : 'Information and the operation of user interface must be understandable.', 
      url_spec    : 'http://www.w3.org/TR/WCAG20/#understandable',
      guidelines : {
        //
        // Guideline 3.1 Readable
        //
        '3.1' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_3_1,  
          title       : 'Guideline 3.1 Readable',
          description : 'Make text content readable and understandable.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#meaning',
          success_criteria : {
            //
            // Success Criteria 3.1.1 Language of Page
            //
            '3.1.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_1_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '3.1.1 Language of Page',
              description    : 'The default human language  of each Web page  can be programmatically determined. ',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#meaning-doc-lang-id',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-doc-lang-id',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-doc-lang-id.html',
              references     : []
            },
            //
            // Success Criteria 3.1.2 Language of Parts
            //
            '3.1.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_1_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '3.1.2 Language of Parts',
              description    : 'The human language of each passage or phrase in the content can be programmatically determined except for proper names, technical terms, words of indeterminate language, and words or phrases that have become part of the vernacular of the immediately surrounding text.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#meaning-other-lang-id',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-other-lang-id',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html',
              references     : []
            },
            //
            // Success Criteria 3.1.3 Unusual Words
            //
            '3.1.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_1_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.1.3 Unusual Words',
              description    : 'A mechanism is available for identifying specific definitions of words or phrases used in an unusual or restricted way, including idioms and jargon.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#meaning-idioms',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-idioms',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-idioms.html',
              references     : []
            },
            //
            // Success Criteria 3.1.4 Abbreviations
            //
            '3.1.4': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_1_4,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.1.4 Abbreviations',
              description    : 'A mechanism for identifying the expanded form or meaning of abbreviations is available. ',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#meaning-located',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-located',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-located.html',
              references     : []
            },
            //
            // Success Criteria 3.1.5 Reading Level
            //
            '3.1.5': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_1_5,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.1.5 Reading Level',
              description    : 'When text requires reading ability more advanced than the lower secondary education level after removal of proper names and titles, supplemental content, or a version that does not require reading ability more advanced than the lower secondary education level, is available. ',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#meaning-supplements',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-supplements',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-supplements.html',
              references     : []
            },
            //
            // Success Criteria 3.1.6 Pronunciation
            //
            '3.1.6': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_1_6,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.1.6 Pronunciation',
              description    : 'A mechanism is available for identifying specific pronunciation of words where meaning of the words, in context, is ambiguous without knowing the pronunciation.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#meaning-pronunciation',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-meaning-pronunciation',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-pronunciation.html',
              references     : []
            }
          }
        },
        //
        // Guideline 3.2 Predictable
        //
        '3.2' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_3_2,  
          title       : 'Guideline 3.2 Predictable',
          description : 'Make Web pages appear and operate in predictable ways.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#consistent-behavior',
          success_criteria : {
            //
            // Success Criteria 3.2.1 On Focus
            //
            '3.2.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_2_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '3.2.1 On Focus',
              description    : 'When any component receives focus, it does not initiate a change of context.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#consistent-behavior-receive-focus',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-receive-focus',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-receive-focus.html',
              references     : []
            },
            //
            // Success Criteria 3.2.2 On Input
            //
            '3.2.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_2_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '3.2.2 On Input',
              description    : 'Changing the setting of any user interface component  does not automatically cause a change of context  unless the user has been advised of the behavior before using the component.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#consistent-behavior-unpredictable-change',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-unpredictable-change',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-unpredictable-change.html',
              references     : []
            },
            //
            // Success Criteria 3..2.3 Consistent Navigation
            //
            '3.2.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_2_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '3.2.3 Consistent Navigation',
              description    : 'Navigational mechanisms that are repeated on multiple Web pages within a set of Web pages  occur in the same relative order each time they are repeated, unless a change is initiated by the user.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#consistent-behavior-consistent-locations',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-consistent-locations',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-consistent-locations.html',
              references     : []
            },
            //
            // Success Criteria 3.2.4 Consistent Identification
            //
            '3.2.4': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_2_4,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '3.2.4 Consistent Identification',
              description    : 'Components that have the same functionality within a set of Web pages are identified consistently.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#consistent-behavior-consistent-functionality',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-consistent-functionality',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-consistent-functionality.html',
              references     : []
            },
            //
            // Success Criteria 3..2.5 Change on Request
            //
            '3.2.5': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_2_5,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.2.5 Change on Request',
              description    : 'Changes of context are initiated only by user request or a mechanism is available to turn off such changes.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#consistent-behavior-no-extreme-changes-context',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-consistent-behavior-no-extreme-changes-context',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/consistent-behavior-no-extreme-changes-context.html',
              references     : []
            }
          }
        },
        //
        // Guideline 3.3 Input Assistance
        //
        '3.3' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_3_3,  
          title       : 'Guideline 3.3 Input Assistance',
          description : 'Help users avoid and correct mistakes.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#minimize-error',
          success_criteria : {
            //
            // Success Criteria 3.3.1 Error Identification
            //
            '3.3.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_3_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '3.3.1 Error Identification',
              description    : 'If an input error is automatically detected, the item that is in error is identified and the error is described to the user in text.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#minimize-error-identified',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-identified',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-identified.html',
              references     : []
            },
            //
            // Success Criteria 3.3.2 Labels or Instructions
            //
            '3.3.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_3_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '3.3.2 Labels or Instructions',
              description    : 'Labels or instructions are provided when content requires user input.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#minimize-error-cues',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-cues',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-cues.html',
              references     : []
            },
            //
            // Success Criteria 3.3.3 Error Suggestion
            //
            '3.3.3': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_3_3,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '3.3.3 Error Suggestion',
              description    : 'If an input error is automatically detected and suggestions for correction are known, then the suggestions are provided to the user, unless it would jeopardize the security or purpose of the content.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#minimize-error-suggestions',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-suggestions',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-suggestions.html',
              references     : []
            }, 
            //
            // Success Criteria 3.3.4 Error Prevention (Legal, Financial, Data)
            //
            '3.3.4': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_3_4,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AA,
              title          : '3.3.4 Error Prevention (Legal, Financial, Data)',
              description    : 'For Web pages that cause legal commitments or financial transactions for the user to occur, that modify or delete user-controllable data in data storage systems, or that submit user test responses, at least one of the following is true:',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#minimize-error-reversible',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-reversible',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-reversible.html',
              references     : []
            },
            //
            // Success Criteria 3.3.5 Help
            //
            '3.3.5': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_3_5,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.3.5 Help',
              description    : 'Context-sensitive help is available.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#minimize-error-context-help',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-context-help',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-context-help.html',
              references     : []
            },
            //
            // Success Criteria 3.3.6 Error Prevention (All)
            //
            '3.3.6': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_3_3_6,
              level          : OpenAjax.a11y.WCAG20_LEVEL.AAA,
              title          : '3.3.6 Error Prevention (All)',
              description    : 'For Web pages that require the user to submit information, at least one of the following is true',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#minimize-error-reversible-all',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-minimize-error-reversible-all',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/minimize-error-reversible-all.html',
              references     : []
            }
          }
        }
      }
    },  
    //
    // Principle 4: Robust
    //
    '4' : {
      id          : OpenAjax.a11y.WCAG20_PRINCIPLE.P_4,
      title       : 'Principle 4: Robust',
      description : 'Content must be robust enough that it can be interpreted reliably by a wide variety of user agents, including assistive technologies.', 
      url_spec    : 'http://www.w3.org/TR/WCAG20/#robust',
      guidelines : {
        //
        // Guideline 4.1 Compatible
        //
        '4.1' : {
          id          : OpenAjax.a11y.WCAG20_GUIDELINE.G_4_1,  
          title       : 'Guideline 4.1 Compatible',
          description : 'Maximize compatibility with current and future user agents, including assistive technologies.', 
          url_spec    : 'http://www.w3.org/TR/WCAG20/#ensure-compat',
          success_criteria : {
            //
            // Success Criteria 4.2.1 Parsing Content
            //
            '4.1.1': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_4_1_1,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '4.1.1 Parsing Content',
              description    : 'In content implemented using markup languages, elements have complete start and end tags, elements are nested according to their specifications, elements do not contain duplicate attributes, and any IDs are unique, except where the specifications allow these features.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#ensure-compat-parses',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-ensure-compat-parses',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-parses.html',
              references     : []
            },
            //
            // Success Criteria 4.2.2 Name, Role, Value
            //
            '4.1.2': {
              id             : OpenAjax.a11y.WCAG20_SUCCESS_CRITERION.SC_4_1_2,
              level          : OpenAjax.a11y.WCAG20_LEVEL.A,
              title          : '4.1.2 Name, Role, Value',
              description    : 'For all user interface components (including but not limited to: form elements, links and components generated by scripts), the name and role can be programmatically determined; states, properties, and values that can be set by the user can be programmatically set; and notification of changes to these items is available to user agents, including assistive technologies.',
              url_spec       : 'http://www.w3.org/TR/WCAG20/#ensure-compat-rsv',
              url_meet       : 'http://www.w3.org/WAI/WCAG20/quickref/#qr-ensure-compat-rsv',
              url_understand : 'http://www.w3.org/TR/UNDERSTANDING-WCAG20/ensure-compat-rsv.html',
              references     : []
            }
          }
        }  
      }
    }
  }
});
 /*
 * Copyright 2011, 2012, 2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* --------------------------------------------------------------------------- */
/*       OpenAjax Alliance Rules National Language Support (NLS): English      */
/* --------------------------------------------------------------------------- */
   

OpenAjax.a11y.all_rules.addRulesNLSFromJSON('en-us', {

    rule_scope: ['unknown', 'Element', 'Page', 'Website'],
    
    message_severities: {
      MUST : 'must', 
      SHOULD: 'should'
    },
    
    rule_categories: {
           '1': 'Abbreviations',
           '2': 'Audio',  
           '4': 'Color and Style',
           '8': 'Controls',
          '16': 'Objects',
          '32': 'Headings',
          '64': 'Images',
         '128': 'Landmarks',
         '256': 'Language',
         '512': 'Layout',
        '1024': 'Links',
        '2048': 'Lists',
        '4096': 'Navigation',
        '8192': 'Scripting',
       '16384': 'Tables',
       '32768': 'Title',
       '65536': 'Timing',
      '131072': 'Video',
      '262144': 'Widgets'
    },

    ACTION_NONE: 'All pass, no action required',

    NOT_APPLICABLE: 'No elements found on the page that apply to this rule',

    //
    //  OAA Rules title and message string National Language Support (NLS)
    //
    rules: {
        AUDIO_1: {
            ID:                    'Audio Rule 1',
            DEFINITION:            'Prerecorded audio only %s have caption or text transcription of the audio content',
            SUMMARY:               'Prerecorded audio %s have alternative',
            TARGET_RESOURCES_DESC: '@object@, @embed@ and @video@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Add caption or text transcript to @audio@, @object@ or @embed@ element',
              ACTION_FAIL_P:  'Add a caption or text transcript to each of the %N_F the @audio@, @object@ or @embed@ elements that failed',
              ACTION_MC_S:    'Check if the @audio@, @object@ or @embed@ element is audio only content.  If it is audio only make sure it has either a caption or text transcript of the audio content',
              ACTION_MC_P:    'Check if any of the %N_MC @audio@, @object@ or @embed@ elements are audio only. If any are audio only make sure they have either a caption or text transcript of the audio',
              NOT_APPLICABLE: 'No @object@, @embed@ or @audio@ elements found on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         '@%1@ element has caption',
              PASS_2:         '@%1@ element has a text transcript',
              ACTION_1:       'Add captions or text transcript to @%1@ element',
              MANUAL_CHECK_1: 'Verify the @%1@ element has captions or text transcript',
              MANUAL_CHECK_2: 'Verify the @%1@ element only renders audio only, if it is audio only verify that it has captions or text transcript'
            },  
            PURPOSE: [
              'Captions and text transcripts provide a means for people cannot hear the audio to understand the audio content'                   
            ],
            TECHNIQUES: [
              'Various techniques to add captions based on the audio format and media players you are supporting, please see your technology specific requirements for captions',
              'The HTML5 video element is attempting to make it easier to support audio descriptions through the use of the text track element',
              'Use aria-describedby attribute to point to a text description of the audio only content'
            ],
            MANUAL_CHECKS: [
              'Check the web page for links to a text transcript of the audio, or if the transcript is part of the page rendering the audio',
              'Check the media player for a button to turn on and off captions',
              'When captions are enabled on the media player, check to make sure the captions visible and represent the speech and sounds heard on the audio',
              'In some cases "open" captions might be used, this means the captions are alway "on" as part of the video'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HMTL 5: The track element', 
                url:   'http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#the-track-element'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'ARIA: aria-describedby', 
                url:   ''
              }                            
            ]
        },
        COLOR_1: {
            ID:                    'Color Rule 1',
            DEFINITION:            'Text content %s exceed Color Contrast Ratio (CCR) of 4.5 for any size text or 3.1 for large and/or bolded text',
            SUMMARY:               'Text %s exceed CCR of 4.5',
            TARGET_RESOURCES_DESC: 'All elements with text content',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Change the foreground and background colors of the text element to meet the CCR > 4.5 requirement',
              ACTION_FAIL_P:     'Change the foreground and background colors of the %N_F text elements to meet the CCR > 4.5 requirement',
              ACTION_MC_S:   'One element requires manual checking for CCR > 4.5 due to the use of a background image',
              ACTION_MC_P:       '%N_MC elements require manual checking for CCR > 4.5 due to the use of background images',
              NOT_APPLICABLE:  'No visible text content on this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'CCR of %1 exceeds 4.5',
              ACTION_1:       'CCR of %1, adjust foreground and background colors to exceed 4.5',
              MANUAL_CHECK_1: 'CCR of %1 is greater than 4.5, but background image may reduce color contrast',
              MANUAL_CHECK_2: 'CCR of %1 is less than or equal to 4.5, but background image may improve color contrast',
              HIDDEN_1:       'CCR of 4.5 not tested since the text is hidden from assistive technologies.'
            },  
            PURPOSE:        [ 'The higher the color contrast of text the more easy it is to read, especially for people with visual impairments'                   
                            ],
            TECHNIQUES:     [ 'Change the foreground color to a more complementary color to the background color',
                              'Change the background color to a more complementary color to the foreground color',
                              'Remove background images or verify they do not compromise color contrast requirements'
                            ],
            MANUAL_CHECKS:  [ 'Use graphic editing tools to analyze the color(s) of the background image and then recacluate the CCR with the range of colors in the background image',
                              'Verify the range of colors that could be part of the background of text is have a CCR > 4.5'
            ],
            INFORMATIONAL_LINKS: [{ type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                               title: 'WCAG 2.0 Success Criterion 1.4.3 Contrast (Minimum): The visual presentation of text and images of text has a contrast ratio of at least 4.5:1', 
                               url:   'http://www.w3.org/TR/WCAG20/#visual-audio-contrast-contrast'
                             },
                             { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                               title: 'How to meet Success Criterion 1.4.3 Contrast (Minimum): The visual presentation of text and images of text has a contrast ratio of at least 4.5:1', 
                               url:   'http://www.w3.org/WAI/WCAG20/quickref/#qr-visual-audio-contrast'
                             }
                            ]
        },
        CONTROL_1: {
            ID:                    'Form Control 1',
            DEFINITION:            '@textarea@, @select@ and @input@ elements of type @text@, @password@, @checkbox@, @radio@ and @file@ %s have an accessible label',
            SUMMARY:               'Controls %s have labels',
            TARGET_RESOURCES_DESC: 'User interface form controls',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add label to form control missing a label',
              ACTION_FAIL_P:     'Add labels to %N_F form controls missing a label',
              NOT_APPLICABLE:  'No @textarea@, @select@ and @input@ elements of type @text@, @password@, @checkbox@, @radio@ and @file@ elements on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     '%1 control has label',
              ACTION_1:   'Add label to %1 control',
              HIDDEN_1:   'Controls % have label was not tested since the %1 control is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'A label associated with a form control insures that information about the form control is spoken by screen readers when it receives focus'                   
            ],
            TECHNIQUES: [
              'The preferred technique for labeling for controls is using the @label@ element and referencing the @id@ attribute value of the form control element',
              'Use the @label@ element to encapsulate the form control element',
              'In special cases, use @aria-labelledby@ attribute to reference the id(s) of the elements on the page that describe the purpose of the form control element',
              'In special cases, use @aria-label@ attribute to provide a explicit text description of the purpose of the form control element'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-LABEL'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H44: Using label elements to associate text labels with form controls', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H44'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H65: Using the title attribute to identify form controls when the label element cannot be used', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H65'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H71: Providing a description for groups of form controls using fieldset and legend elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H71'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Labels for Form Controls Overview', 
                url:   'http://html.cita.illinois.edu/nav/form/'
              }                            
            ]
        },
        CONTROL_2: {
            ID:                    'Form Control 2',
            DEFINITION:            'Every @input@ type @image@ %s have an @alt@ or @title@ attribute with content',
            SUMMARY:               'Image button %s have alt content',
            TARGET_RESOURCES_DESC: 'input elements of type image',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add @alt@ attribute to label the @input[type="image"]@ element missing a missing a label',
              ACTION_FAIL_P:     'Add @alt@ attribute to label the %N_F @input[type="image"]@ elements missing labels',
              NOT_APPLICABLE:  'No @input[type="image"]@ elements on this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'Image button has label',
              ACTION_1: 'Add @alt@ attribute with text content',
              ACTION_2: 'Add text content to the @alt@ attribute',
              HIDDEN_1: 'Image button was not tested for @alt@ attribute because button is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'A label associated with a form control insures that information about the form control is spoken by screen readers when it receives focus'                   
            ],
            TECHNIQUES: [
              'The preferred technique for labeling for controls is using the @label@ element and referencing the @id@ attribute value of the form control element',
              'Use the @label@ element to encapsulate the form control element',
              'In special cases, use @aria-labelledby@ attributes to reference the id(s) of the elements on the page that describe the purpose of the form control element',
              'In special cases, use @aria-label@ attributes to provide a explicit text description of the purpose of the form control element'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-LABEL'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H36: Using alt attributes on images used as submit buttons', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H36'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Labels for Form Controls Overview', 
                url:   'http://html.cita.illinois.edu/nav/form/'
              }                            
            ]
        },
        CONTROL_3: {
            ID:                    'Form Control 3',
            DEFINITION:            'Every input type radio %s be contained in a @fieldset@ and @legend@ elements to provide grouping information for radio button groups',
            SUMMARY:               'Radio button %s use @fieldset/legend@',
            TARGET_RESOURCES_DESC: 'input elements of type radio',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @filedset/legend@ labeling container for the @input[type="radio"]@ element NOT in a grouping container',
              ACTION_FAIL_P:       'Add @filedset/legend@ labeling container for each group of the %N_F @input[type="radio"]@ elements NOT in a grouping container',
              NOT_APPLICABLE:    'No @input[type="radio"]@ elements on this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'Radio button uses @fieldset@ and @legend@ elements, and the @legend@ element has text content',
              MANUAL_CHECK_1: 'Radio button uses aria-labelledby, verify the label text content includes group information',
              MANUAL_CHECK_2: 'Radio button uses aria-label, verify the label text content includes group information',
              ACTION_1:       'Add a @legend@ element to with text content to the @fieldset@ element to provide grouping label information for the radio buttons',
              ACTION_2:       'Add a @fieldset@ and @legend@ elements to provide grouping label information for the radio buttons',
              HIDDEN_1:       '@fieldset/legend@ for radio button was not tested because radio button is hidden from assistive technology'
            },  
            PURPOSE: [
              'Radio buttons need a common grouping label to provide a context for each radio button option'
            ],
            TECHNIQUES: [
              '@fieldset@/@legend@ element combination is the preferred technique to provide a grouping label for each group of radio buttons',
              '@aria-labelledby@ attribute can provide a grouping label for radio buttons',
              '@aria-label@ attributes can provide a grouping label for radio buttons'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @fieldset@ and @legend@ elements', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-FIELDSET'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H71: Providing a description for groups of form controls using fieldset and legend elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H71'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H90: Indicating required form controls using label or legend', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H90'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA: Example 7: Fieldset/Legend for grouping radio buttons', 
                url:   'http://html.cita.illinois.edu/nav/form/radio/index.php?example=6'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist: HTML example 6', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webstructure.html'
              }                            
            ]
        },
        CONTROL_4: {
            ID:                    'Form Control 4',
            DEFINITION:            '@button@ elements %s have text content',
            SUMMARY:               '@button@s %s have content',
            TARGET_RESOURCES_DESC: '@button@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add text content to @button@ element that describe the purpose of the button',
              ACTION_FAIL_P:     'Add text content to %N_F @button@ elements that describe the purpose of each button',
              NOT_APPLICABLE:  'No @button@ elements on this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@button@ element has text content',
              ACTION_1: 'Add text content to @button@ element',
              HIDDEN_1:   'Content for  @button@ element was not tested because @button@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'The text content of a @button@ element is used as a label to insure that the purpose of the button is spoken by screen readers when the button receives focus'                   
            ],
            TECHNIQUES: [
              'The text content of the @button@ element and the @alt@ attribute content of images inside the button is used as the text content'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @button@ elements', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-BUTTON'
              }
            ]
        },
        CONTROL_5: {
            ID:                    'Form Control 5',
            DEFINITION:            '@id@ attributes %s have unique values on the web page',
            SUMMARY:               '@id@ %s be unique',
            TARGET_RESOURCES_DESC: 'Form control elements with @id@ attributes',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_P:     'Update elements with @id@ attributes so the id values are all unique',
              NOT_APPLICABLE:  'No elements or only one element with an @id@ attribute on this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     '\'%1\' @id@ attribute value is unique',
              ACTION_1:   'Update elements that share the \'%1\' @id@ value to have unique @id@ values',
              HIDDEN_1:     '@id@ was not tested for uniquness because %1 control element is hidden from assistive technology'
            },  
            PURPOSE: [
              '@id@ attribute values can be used as references by @label@ elements, if @id@ attribute values are not unique it can result incorrect labeling of form controls',
              '@aria-labelledby@ and @aria-describedby@ atributes also depend on unique @id@ values for labeling and adding descriptions to form controls.'
            ],
            TECHNIQUES: [
              'If a form control defines an @id@ attribute, make sure the value is unique on the page'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: @id@ attribute', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#adef-id'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F77: Failure of Success Criterion 4.1.1 due to duplicate values of type ID', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F77'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H88: Using HTML according to spec', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H88'
              }                             
            ]
        },
        CONTROL_6: {
            ID:                    'Form Control 6',
            DEFINITION:            '@label@ element using the @for@ attribute %s reference a form control on the page',
            SUMMARY:               '@label@ %s reference control',
            TARGET_RESOURCES_DESC: '@label@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Change the @label@ element to use the @for@ attribute to label its form control',
              ACTION_FAIL_P:     'Change the %N_F @label@ elements to use the @for@ attribute to label their form control',
              NOT_APPLICABLE:  'No @label@ elements on this page'
            },
            NODE_RESULT_MESSAGES: {
              ACTION_1:   'Change the @label@ element @for@ attribute to reference \'%1\' to reference a form control'
            },  
            PURPOSE: [
              '@label@ elements only are useful for accessibility when they reference or encapsulate form controls'                   
            ],
            TECHNIQUES: [
              '@label@ elements using the FOR attribute need to reference a form control with the corresponding @id@ attribute value'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element FOR attribute', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#adef-for'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H44: Using label elements to associate text labels with form controls', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H44'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA: Using @label@ Element for Labeling Form Controls', 
                url:   'http://html.cita.illinois.edu/nav/form/form-label-markup.php'
              }                             
            ]
        },
        CONTROL_7: {
            ID:                    'Form Control 7',
            DEFINITION:            '@label@ element or @legend@ element %s contain text content',
            SUMMARY:               '@label@ %s have content',
            TARGET_RESOURCES_DESC: '@label@ and @legend@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add text content to the @label@ or @legend@ element that describes the purpose of a form control or groupings of form controls, or remove the element if it is not needed for labeling',
              ACTION_FAIL_P:  'Add text content to the %N_F @label@ or @legend@ elements to describes the purpose of each form control or groupings of form controls, or remove element(s) if they are not needed for labeling',
              NOT_APPLICABLE: 'No @label@ or @legend@ elements on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     '@%1@ has text content',
              ACTION_1:   'Add text content to the @%1@ element or if not needed remove from page',
              HIDDEN_1:   'Content in labels is not tested because @%1@ element is hidden from assistive technologies',
              HIDDEN_2:   'Content in @%1@ element is not tested because the referenced @%2@ control is hidden from assistive technologies'
            },
            PURPOSE: [
              'For @label@ and @legend@ elements only are useful for accessibility when they contain content'                   
            ],
            TECHNIQUES: [
              'Add text content to @label@ and @legend@ elements that help describe the purpose of the form control'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element @for@ attribute', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#adef-for'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H44: Using @label@ elements to associate text labels with form controls', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H44'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H88: Using HTML according to spec', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H88'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA: Using @label@ element for Labeling Form Controls', 
                url:   'http://html.cita.illinois.edu/nav/form/form-label-markup.php'
              }                             
            ]
        },
        CONTROL_8: {
            ID:                    'Form Control 8',
            DEFINITION:            '@fieldset@ element %s contain exactly one legend element',
            SUMMARY:               '@fieldset@ %s have one legend',
            TARGET_RESOURCES_DESC: '@fieldset@ and @legend@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Update @fieldset@ element to contain only one @legend@ element',
              ACTION_FAIL_P:     'Update %N_F @fieldset@ elements to contain only one @legend@ element',
              NOT_APPLICABLE:  'No @fieldset@ elements on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     '@fieldset@ has one @legend@ element',
              ACTION_1:   'Add @legend@ element',
              ACTION_2:   'Remove %1 @legend@ elements',
              ACTION_3:   '@legend@ element is hidden from asssitive technology, use CSS off screen positioning instead of CSS display or visibility properties to remove legend from graphical rendering',
              HIDDEN_1:   'Only one @legend@ in @fieldset@ element is not tested because the @fieldset@ is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'Multiple legend elements contained in the same fieldset may result in the improper calculation of labels for assistive technologies.'                   
            ],
            TECHNIQUES: [
              '@fieldset@ element should have one and only one @legend@ elements to help describe the purpose of the form controls contained in the @fieldset@ element'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: Adding structure to forms: the @fieldset@ and @legend@ elements', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-FIELDSET'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H71: Providing a description for groups of form controls using fieldset and legend elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H71'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H88: Using HTML according to spec', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H88'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA: Using @label@ element for Labeling Form Controls', 
                url:   'http://html.cita.illinois.edu/nav/form/form-label-markup.php'
              }                             
            ]
        },
        CONTROL_9: {
            ID:                    'Form Control 9',
            DEFINITION:            '@title@ attribute may not be good a label for form control',
            SUMMARY:               '@title@ may not be good label',
            TARGET_RESOURCES_DESC: '@textarea@, @select@ and @input@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:  'Verify the @title@ attribute is is a good label for the form control, and not as a tooltip',
              ACTION_MC_P:      'Verify the @title@ attribute is is a good label for %N_F form controls, and that they are not being used as a only a tooltip',
              NOT_APPLICABLE: 'No form controls on this page with a @title@ attribute'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         '@title@ is not used as label',
              MANUAL_CHECK_1: 'Use @label@ element or ARIA technique to label %1 form control instrad of the @title@ attribute',
              HIDDEN_1:         '@title@ not a good label was not tested because %1 control element is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'When the @title@ attribute is used for tooltips it is often uses more words than needed to label a form control for users of assistive technologies',                   
              'use @aria-label@ to provide a shorter label to users of assistive technologies if the @title@ attribute content is determined to not be an optimal label' 
            ],
            TECHNIQUES: [
              'The preferred technique for labeling for controls is using the @label@ element and referencing the @id@ attribute value of the form control element',
              'Use the @label@ element to encapsulate the form control element',
              'In special cases, use @aria-labelledby@ attributes to reference the id(s) of the elements on the page that describe the purpose of the form control element',
              'In special cases, use @aria-label@ attributes to provide a explicit text description of the purpose of the form control element'
            ],
            MANUAL_CHECKS: [
              'If the @title@ attribute is the labeling technique of last resort, use other labeling form labeling techniques',
              'Reserve the @title@ attribute for tooltips if they are needed for your form'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: Adding structure to forms: the @fieldset@ and @legend@ elements', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-FIELDSET'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H88: Using HTML according to spec', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H88'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA: Using @label@ element for Labeling Form Controls', 
                url:   'http://html.cita.illinois.edu/nav/form/form-label-markup.php'
              }                             
            ]
        },
        CONTROL_10: {
            ID:                    'Form Control 10',
            DEFINITION:            'Labels %s be unique for every textarea, select and input element of type text, password, radio, and checkbox on a page',
            SUMMARY:               'Labels %s be unique',
            TARGET_RESOURCES_DESC: '@textarea@, @select@ and @input@ elements of type @text@, @password@, @checkbox@, @radio@ and @file@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_P:    'Update the labels for the %N_F form controls with duplicate labels to uniquely identify the purpose of each form control on the page',
              NOT_APPLICABLE: 'No form controls or only one form control on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'Label is unique',
              ACTION_1: 'Add label to %1 control',
              ACTION_2: 'Change the @label@ element content, use @fieldset@/@legend@ elements or ARIA technique to make label text content unique on the page',
              HIDDEN_1:   'Unique form control label was not test because the %1 control element is hidden from assistive technology.'
            },  
            PURPOSE: [
              'Labels that are unique make it possible for people to understand the different purposes of form controls on the same page'                   
            ],
            TECHNIQUES: [
              'The preferred technique for labeling for controls is using the @label@ element and referencing the @id@ attribute value of the form control element',
              'Use the @label@ element to encapsulate the form control element',
              'In special cases, use @aria-labelledby@ attribute to reference the id(s) of the elements on the page that describe the purpose of the form control element',
              'In special cases, use @aria-label@ attribute to provide a explicit text description of the purpose of the form control element'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-LABEL'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H44: Using label elements to associate text labels with form controls', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H44'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H65: Using the title attribute to identify form controls when the label element cannot be used', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H65'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H71: Providing a description for groups of form controls using fieldset and legend elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H71'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Labels for Form Controls Overview', 
                url:   'http://html.cita.illinois.edu/nav/form/'
              }                            
            ]
        },
        CONTROL_11: {
            ID:                    'Form Control 11',
            DEFINITION:            'If there is more than one form on a page, @input@ element of type @submit@ and @reset@ %s have unique labels using the value attribute',
            SUMMARY:               '@submit@ and @reset@ buttons %s be unique',
            TARGET_RESOURCES_DESC: '@submit@ and @reset@ buttons',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_P:     'Change the labeling of %N_F @submit@ or @reset@ buttons to uniquely identify the purpose of each @submit@ or @reset@ buttons on the page',
              NOT_APPLICABLE:  'No forms or only one form on this page'                            
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     'Label is unique',
              ACTION_1:   'Add label to %1 control',
              ACTION_2:   'Change the @value@ attribute content, use @fieldset@/@legend@ elements or ARIA technique to make @submit@ and @reset@ labels unique on the page',
              HIDDEN_1:     '@submit@ and @reset@ button labels being unique was not tested because %1 control element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Labels that are unique make it possible for people to understand the different purposes of form controls on the same page',                   
              '@submit@ and @reset@ form controls have default labels and if there is more than one form on a page the user may not understand which form they are submitting'                   
            ],
            TECHNIQUES: [
              'The preferred technique for changing the default label for @submit@ and @reset@ controls is the @value@ attribute',
              'In special cases, use @aria-labelledby@ attribute to reference the id(s) of the elements on the page that describe the purpose of the form control element',
              'In special cases, use @aria-label@ attribute to provide a explicit text description of the purpose of the form control element'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-LABEL'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H44: Using label elements to associate text labels with form controls', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H44'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H65: Using the title attribute to identify form controls when the label element cannot be used', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H65'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H71: Providing a description for groups of form controls using fieldset and legend elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H71'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Labels for Form Controls Overview', 
                url:   'http://html.cita.illinois.edu/nav/form/'
              }                            
            ]
        },
        CONTROL_12: {
            ID:                    'Form Control 12',
            DEFINITION:            'Labels %s be must describe the purpose of every button, textarea, select and input element of type text, password, radio, and checkbox on a page',
            SUMMARY:               'Labels %s be descriptive',
            TARGET_RESOURCES_DESC: '@textarea@, @select@ and @input@ elements of type @text@, @password@, @checkbox@, @radio@ and @file@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add label to the form control that describes the from controls purpose',
              ACTION_FAIL_P:     'Add labels, to the %N_F form controls missing labels, that uniquely describe purpose of each from control',
              ACTION_MC_S:   'Verify the label uniquely describes the purpose of the control',
              ACTION_MC_P:       'Verify the labels for the %N_MC form controls uniquely describe the purpose of the control',
              NOT_APPLICABLE:  'No form controls on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: 'Verify the label describes the purpose and input required for @%1@ form control',
              ACTION_1:       'Add a @label@ element, use @fieldset@/@legend@ elements or ARIA technique to provide a label for @%1@ form control',
              HIDDEN_1:       'Form control label is descriptive was not tested because %1 control element is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'Labels that are unique make it possible for people to understand the different purposes of form controls on the same page'                   
            ],
            TECHNIQUES: [
              'The preferred technique for labeling for controls is using the @label@ element and referencing the @id@ attribute value of the form control element',
              'Use the @label@ element to encapsulate the form control element',
              'In special cases, use @aria-labelledby@ attribute to reference the id(s) of the elements on the page that describe the purpose of the form control element',
              'In special cases, use @aria-label@ attribute to provide a explicit text description of the purpose of the form control element',
              'Use @aria-describedby@ to provide references to instructions or error information'
            ],
            MANUAL_CHECKS: ['Good labels are both concise and descriptive of the form control purpose',
                            'If form controls are arranged in groups, use @filedset/Legend@ or ARIA labeling techniuqes to include grouping information',
                            'Consider using @aria-describedby@ to provide references to instructions or error information'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @label@ element', 
                url:   'http://www.w3.org/TR/html4/interact/forms.html#edef-LABEL'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H44: Using label elements to associate text labels with form controls', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H44'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H65: Using the title attribute to identify form controls when the label element cannot be used', 
                url:   'http://www.w3.org/TR/2010/NOTE-WCAG20-TECHS-20101014/H65'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H71: Providing a description for groups of form controls using fieldset and legend elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H71'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Labels for Form Controls Overview', 
                url:   'http://html.cita.illinois.edu/nav/form/'
              }                            
            ]
        },    
        FOCUS_1: {
            ID:                    'Focus Rule 1',
            DEFINITION:            'The sequential focus order of links, form controls, embedded apps and widgets %s be meaningful',
            SUMMARY:               'Focus order %s be meaningful',
            TARGET_RESOURCES_DESC: '@a@, @area@, @input@, @textarea@ and @select@ elements and elements with widget roles with @tabindex@ values',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:       'Check the "tab" focus order of the page to make sure the seuquence of focusable elements is meaningful',
              ACTION_MC_P:       'Check the "tab" focus order of the page to make sure the seuquence of focusable elements is meaningful',
              NOT_APPLICABLE:    'No or only one focusable element on the page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: 'Use the "tab" key to check the focus order of the %1 interactive elements on the page (i.e. links, form controls, ...)',
              MANUAL_CHECK_2: 'Use the "tab" key to check the focus order of the %1 interactive elements on the page (i.e. links, form controls, ...); NOTE: %2 other interactive elements on the page have been removed from the tab order by setting the @tabindex@ value to less than 0',
              PAGE_1:         '%1 element with @role@="%2" is part of the sequential focus order manual check',
              PAGE_2:         '%1 element is part of the sequential focus order manual check',
              PAGE_3:         '%1 element with @role@="%2" has a @tabindex@="%2", so it is NOT part of the sequential focus oarder of the page',
              PAGE_4:         '%1 element has a @tabindex@="%2", so it is NOT part of the sequential focus order of the page',
              HIDDEN_1:       '%1 element with @role@="%2" is hidden, so NOT a part of the sequential focus order of the page',
              HIDDEN_2:       '%1 element is hidden, so NOT a part of the sequential focus order of the page'
            },  
            PURPOSE: [
              'The "tab" key is the primary key many browsers use to navigate the interactive elements on a web page',
              'The sequential order of the elements receiving focus can help a user understand the features on a web page',
              'The usability of frequently used or important interactive features of a web page can be improved by moving them to the beginning of the focus sequence'
            ],
            TECHNIQUES: [
              'Use document order to place related interactive items seuq',
              'The @tabindex@ atttribute value (i.e. values greater than 0) can be used to change the sequence of focusable elements in a web page or make non-interactive elements part of the "tab" order of the page',
              'A @tabindex@ values of less than 0 remove redundent interactive elements from the sequential focus order'
            ],
            MANUAL_CHECKS: [
              'Use the "tab" key to move focus through the links, form controls, embedded applications and widgets on the page',
              'Does the sequence of elements receiving focus make sense (i.e. related items on the page are navigated sequentially as a group)'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G59: Placing the interactive elements in an order that follows sequences and relationships within the content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G59'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H4: Creating a logical tab order through links, form controls, and objects', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H4'
              }                            
            ]
        },
        FOCUS_2: {
            ID:                    'Focus Rule 2',
            DEFINITION:            'The element with keyboard focus %s have a visible focus style that is different from the non-focus state',
            SUMMARY:               'Focus %s be visible',
            TARGET_RESOURCES_DESC: '@a@, @area@, @input@, @textarea@ and @select@ elements and elements with widget roles with @tabindex@ values',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:       'Use the "tab" key to move focus between links, form controls, embedded apps and widgets and check the visibility of focus styling for each element as it receives focus',
              ACTION_MC_P:       'Use the "tab" key to move focus between links, form controls, embedded apps and widgets and check the visibility of focus styling for each element as it receives focus',
              NOT_APPLICABLE:    'No focusable elements on the page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: 'Use keyboard commands to check the keyboard focus styling of the %1 interactive elements on the page (i.e. links, form controls, ...)',
              MANUAL_CHECK_2: 'Use keyboard commands to check the keyboard focus styling of the %1 interactive elements on the page (i.e. links, form controls, ...); NOTE: %2 interactive elements are hidden',
              PAGE_1:         '%1 element with @role@="%2" is part of the keyboard focus styling manual check',
              PAGE_2:         '%1 element is part of the keyboard focus styling manual check',
              HIDDEN_1:       '%1 element with @role@="%2" is hidden, so is not visible for changing the focus styling',
              HIDDEN_2:       '%1 element is hidden, so is not visible for changing the focus styling'
            },  
            PURPOSE: [
              'Many browsers don\'t provide a prominent or consistent visible keyboard focus styling for interactive elements, making it difficult for users to identify and track the element with keyboard focus',
              'Author defined visible keyboard focus style makes it easier for users to know which interactive element has keyboard focus and provides more consistent user experience between browsers and operating systems.'
            ],
            TECHNIQUES: [
              'Use CSS psuedo element selector @:focus@ to change the styling of elements with keyboard focus',
              'Use @focus@ and @blur@ events on checkboxes and radio buttons to change the styling of not only the form control, but also its label text to make it easier to see',
              'Styling changes should include creating a border around the interactive element and its label, typically using the CSS @border@ or @outline@ properties',
              'For consistent look and feel to the website it is often useful for the focus and hover styles to be the same or similar'
            ],
            MANUAL_CHECKS: [
              'Use the the keyboard (i.e. typically he "tab" key, but in the case of widgets other keys) to move focus through the links, form controls, embedded applications and widgets on the page',
              'Check if the element with keyboard focus is clearly visible for all focusable elements on the page as you move focus between elements, and that it changes more than just color (i.e. border/outline around element with focus)',
              'Test keyboard focus styling using more than one browser and operating system, since there is a wide varability of between operating systems and browsers for styling keyboard focus'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'C15: Using CSS to change the presentation of a user interface component when it receives focus ', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/C15'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G195: Using an author-supplied, highly visible focus indicator', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G195'
              }                            
            ]
        },    
        FOCUS_3: {
            ID:                    'Focus Rule 3',
            DEFINITION:            'The target of a link %s result in focus the content the window if the target results in more than one window opening',
            SUMMARY:               'Target focus %s be in content window',
            TARGET_RESOURCES_DESC: '@a@, @area@ and @role="link"@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:       'Check the link to make sure that if the link opens more than one window that the focus is in the content window',
              ACTION_MC_P:       'Check the $N_MC links to make sure that if any of the links opens more than one window that the focus is in the content window',
              NOT_APPLICABLE:    'No link elements on the page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: 'If the target of the link opens multiple windows (i.e. typically advertisements or other promotional information) make sure keyboard focus is on the content window',
              HIDDEN_1:       '%1 element is hidden, so cannot open any new windows'
            },  
            PURPOSE: [
              'User\'s can become disoriented if the focus causes unpredicatable actions, including new URLs and popup windows for advertisements or promotions.'
            ],
            TECHNIQUES: [
              'Do not link to URLs that open multiple windows and do not manage the focus to be in the content windoow the user was expecting by following the link'
            ],
            MANUAL_CHECKS: [
              'After selecting a link and if it opens multiple windows, make sure the keyboard focus is in the content window'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G200: Opening new windows and tabs from a link only when necessary', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G200'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G201: Giving users advanced warning when opening a new window', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G201'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F52: Failure of Success Criterion 3.2.1 and 3.2.5 due to opening a new window as soon as a new page is loaded', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F52'
              }                            
            ]
        },    
        FOCUS_4: {
            ID:                    'Focus Rule 4',
            DEFINITION:            '@select@ elements with @onchange@ event %s not automatically change the user\'s context when keyboard focus moves between options',
            SUMMARY:               '@select@ %s not change context',
            TARGET_RESOURCES_DESC: '@a@, @area@ and @role="link"@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:       'Check the @select@ element to make sure that when keyboard focus moves between options does not cause a change in context (e.g. moving to a new URL or focus being moved from the @select@ element)',
              ACTION_MC_P:       'Check the %N_MC @select@ elements to make sure that when keyboard focus moves between options in each control does not cause a change in context (e.g. moving to a new URL or focus being moved from the @select@ element)',
              NOT_APPLICABLE:    'No @select@ elements on the page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: 'Check to make sure moving keyboard focus between options in the @select@ box does not move focus from the list of options',
              HIDDEN_1:       '@select@ element is hidden'
            },  
            PURPOSE: [
              'User\'s can become disoriented if the focus changes cause unpredicatable actions.',
              'When the user is using the kyboard to explore @select@ box options, the focus must stay on the options, until the user selects one of the options'
            ],
            TECHNIQUES: [
              'Do not use @onchange@ event handlers on @select@ elements',
              'Use selections should be made using the enter key'
            ],
            MANUAL_CHECKS: [
              'Move focus to the @selection@ box and use the keyboard to move the focus between options, check to make sure the focus changes are not causing the context to change (i.e. focus movig to a new window or focus moving from the current option in the select box)'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G200: Opening new windows and tabs from a link only when necessary', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G200'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G201: Giving users advanced warning when opening a new window', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G201'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F52: Failure of Success Criterion 3.2.1 and 3.2.5 due to opening a new window as soon as a new page is loaded', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F52'
              }                            
            ]
        },        
        HEADING_1: {
            ID:                    'Heading Rule 1',
            DEFINITION:            'Each page %s contain at least one @h1@ element and each @h1@ element must have content',
            SUMMARY:               'Page %s have @h1@ element',
            TARGET_RESOURCES_DESC: '@h1@ and @body@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_P:    'Add a @h1@ element with at the beginning of the main content of the page that describes the main content of the page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    'Page has @h1@ element',
              ACTION_1:  'Add a @h1@ element at the beginning of the main content of the page',
              ACTION_2:  'Add content to the @h1@ element, the content should describe the content or the purpose of the page or main section',
              HIDDEN_1:  '@h1@ element is hidden from assistive technology and therefore cannot be used satisfy each page having an @h1@ element requirement'
            },  
            PURPOSE: [
              'Headings provide a navigation point for users of assistive technologies to the main content and help users understand the main content of the page'                   
            ],
            TECHNIQUES: [
              'Include an @h1@ element at the beginning of the main content',
              'The text content of the @h1@ element should describe the main content of the page',
              'The @h1@ element should be visible graphically and to assistive technologies, do not hide using CSS techniques'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @h1@ element', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-H1'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G130: Providing descriptive headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G130'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G141: Organizing a page using headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G141'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Unique Title', 
                url:   'http://html.cita.illinois.edu/nav/title/'
              }                            
            ]
        },    
        HEADING_2: {
            ID:                    'Heading Rule 2',
            DEFINITION:            'If the page contains both a @main@ landmark and an @h1@ element, the @h1@ element %s be a child of the main landmark.',
            SUMMARY:               '@h1@ %s be in @main@ landmark',
            TARGET_RESOURCES_DESC: '@h1@ elements and elements with ARIA attribute @role="main"@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Move the @h1@ element to the beginning of a @main@ landmark or change the @h1@ element to another heading level',
              ACTION_FAIL_P:  'Move the %N_F @h1@ elements to the beginning of a @main@ landmark or change the @h1@ element to another heading level',              
              NOT_APPLICABLE: 'No @main@ landmark and/or @h1@ elements in this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@h1@ is a child element of a @main@ landmark',
              ACTION_1: 'Position the @h1@ element as one of the first descendant elements of a @main@ landmark',
              HIDDEN_1: '@h1@ element being in @main@ landmark was not tested because the @h1@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Headings provide a navigation point for users of assistive technologies to the primary content and help users understand that content',
              'Including both @main@ landmarks and @h1@ elements provides a redundant way for users of assistive technology to find the main topics of a web page'
            ],
            TECHNIQUES: [
              'This rule supports a coding practice of reserving the @h1@ element for titling for the main content areas of a web page',
              'Include an @h1@ element at the beginning of each @main@ landmark',
              'The @h1@ element should describe the main content or the purpose of the page',
              'If there is more than one @main@ landmark, use @aria-labelledby@ on the @main@ landmark to reference the @h1@ element as a name for @main@ landmark'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: The @h1@ element', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-H1'
              },                            
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: @main@ role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#main'
              }                            
            ]
        },    
        HEADING_3: {
            ID:                    'Heading Rule 3',
            DEFINITION:            'Sibling heading elements accessible names %s should be unique',
            SUMMARY:               'Sibling headings %s be unique',
            TARGET_RESOURCES_DESC: 'Heading elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_P:      'Update the text content of the %N_F sibling heading elements of the same level to be unique',              
              NOT_APPLICABLE:   'No sibling heading elements of the same level in this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    '%1 heading content is unique among sibling headings',
              ACTION_1:  'Change %1 heading content to describe the differences sibling sections',
              HIDDEN_1:  'Sibling headings being unique was not tested because %1 element is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'If section headings that share the same parent heading are NOT unique users of assistive technology will not be able to discern the differences between sibling sections of the web resource'
            ],
            TECHNIQUES: [
              'Make sure the content of sibling headings that share the same parent heading help users understand the unique content of each section they describe'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: Headings: The H1, H2, H3, H4, H5, H6 elements', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-H1'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G130: Providing descriptive headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G130'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G141: Organizing a page using headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G141'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Unique Title', 
                url:   'http://html.cita.illinois.edu/nav/title/'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Sub Headings', 
                url:   'http://html.cita.illinois.edu/nav/heading/'
              }                            
            ]
        },    
        HEADING_4: {
            ID:                    'Heading Rule 4',
            DEFINITION:            'Heading elements %s describe the sections they label',
            SUMMARY:               'Headings %s be descriptive',
            TARGET_RESOURCES_DESC: 'Heading elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Add text content to heading',
              ACTION_FAIL_P:  'Add text content to %N_F heading elements with no text content',
              ACTION_MC_S:    'Verify the heading element describes the content following the heading element',
              ACTION_MC_P:    'Verify the %N_MC heading elements describe the section following each heading element',
              NOT_APPLICABLE: 'No heading elements on this page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1:  'Check %1 element to make sure it describes the section it labels',
              ACTION_1:        'Add text content to %1 element that describes the section it labels or remove it from the page if it is not needed',
              HIDDEN_1:        'Heading elements must be descriptive was not tested because %1 element is hidden from assistive technology'
            },  
            PURPOSE: [
              'If headings are NOT descriptive or unique they will confuse users of assistive technology',
              'There is a balance on having too few headings and too many headings on a web page, try to make sure that the heading structure provides a good "outline" of the sections and sub sections of a web page'
            ],
            TECHNIQUES: [
              'Use the @h1@ element to indicate the start of the main content, by putting it at the right before the main content',
              'Use the @h1@ element to provide a title for the web page',
              'Include headings elements at the proper level for each section of a web page',
              'Use @aria-describedby@ to provide context for a heading in a web page',
              'Use headings as labels for ARIA landmarks to provide redundant page navigation capabilities for users of assistive technologies',
              'Use CSS @position@ property to remove headings from the graphical rendering, but still be visible to assistive technology users',
              'Do NOT use CSS @display: none@, @visibility: hidden@ or the @hidden@ attribute to hide headings when they are being used to provide section information to assistive technology users, instead use CCS @position@ property to move the heading off screen'
            ],
            MANUAL_CHECKS: [
              'Check heading structure to make sure headings represent the sections and sub sections of the web page',
              'Check headings against other headings on the page to make sure the headings uniquely describe content of each section of the web page',
              'If headings are too similar to each other users of assistive technology will not be able to use them to understand the differences between different sections of the web page'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: Headings: The H1, H2, H3, H4, H5, H6 elements', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-H1'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G130: Providing descriptive headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G130'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G141: Organizing a page using headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G141'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Unique Title', 
                url:   'http://html.cita.illinois.edu/nav/title/'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Sub Headings', 
                url:   'http://html.cita.illinois.edu/nav/heading/'
              }                            
            ]
        },    
        HEADING_5: {
            ID:                    'Heading Rule 5',
            DEFINITION:            'Heading elements %s be properly nested',
            SUMMARY:               'Headings %s be properly nested',
            TARGET_RESOURCES_DESC: 'Heading elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Review the heading struture of the entire page and update the heading levels so the heading element within the %N_T headings on the page are properly nested',
              ACTION_FAIL_P:  'Review the heading struture of the entire page and update the heading levels so the %N_F heading elements within the %N_T headings on the page are properly nested',
              NOT_APPLICABLE: 'No heading elements or only one heading element on this page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '%1 element is properly nested',
              PASS_2:   'All heading elements are properly nested',
              ACTION_1: 'Adjust the the level of the %1 element or other heading elements to make the headings properly nested',
              ACTION_2: 'Add text content to %1 element that describes the section it labels or remove it from the page if it is not needed',
              ACTION_3: 'One heading element is not properly nested, check all heading elements to make sure they are properly nested, and describe the structure and the sections of the web page',
              ACTION_4: '%1 heading elements are not properly nested, check all heading elements to make sure they are properly nested, and describe the structure and the sections of the web page',
              HIDDEN_1: 'Headings properly nested rule was not tested because the %1 element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Heading elements that are properly nested help users of assistive technology understand the structure of the information on the web page'
            ],
            TECHNIQUES: [
              'Include headings elements at the proper level for each section of a web page',
              'Use headings as labels for ARIA landmarks to provide a redundant way for asssistive technology users to navigate the page (i.e. header or landmark navigation)',
              'Check headings against other headings in the document to make sure the headings uniquely describe content of each section of the web page',
              'If headings are too similar to each other users of assistive technology will not be able to use them to understand the differences between sections of the web page'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: Headings: The H1, H2, H3, H4, H5, H6 elements', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-H1'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G130: Providing descriptive headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G130'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G141: Organizing a page using headings', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G141'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Unique Title', 
                url:   'http://html.cita.illinois.edu/nav/title/'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Sub Headings', 
                url:   'http://html.cita.illinois.edu/nav/heading/'
              }                            
            ]
        },    
        HEADING_6: {
            ID:                    'Heading Rule 6',
            DEFINITION:            'Heading elements %s not consist only just image content',
            SUMMARY:               'Headings %s not be just images',
            TARGET_RESOURCES_DESC: 'Heading elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Use CSS techniques to style text instead of an image for the heading',
              ACTION_FAIL_P:  'Use CSS techniques to style text instead of an image for the %N_F headings',
              NOT_APPLICABLE: 'No headings with only image content'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '%1 element does contain visible text content',
              ACTION_1: 'Add visible text content to the %1 element',
              HIDDEN_1: 'Heading elements with only @img@ element content was not tested because the %1 element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Head elements that consist only of image content are not easily restyled for people with low vision to see.'
            ],
            TECHNIQUES: [
              'Use CSS instead of images to style heading text content'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: Headings: The H1, H2, H3, H4, H5, H6 elements', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-H1'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'C22: Using CSS to control visual presentation of text', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/C22'
              }                             
            ]
        },    
        IMAGE_1: {
            ID:                    'Image Rule 1',
            DEFINITION:            'Each image %s have an text alternative',
            SUMMARY:               'Image %s have alternative',
            TARGET_RESOURCES_DESC: '@img@, @area@ and [role="img"]',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Add @alt@, @aria-labelledby@ or @aria-label@ attribute to image element that describes the purpose of the image',
              ACTION_FAIL_P:  'Add @alt@, @aria-labelledby@ or @aria-label@ attribute to each of the %N_F out of %N_T image elements that describes the content and/or purpose of each image',
              NOT_APPLICABLE: 'No @img@, @area@ or @[role=img]@ elements found on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@%1@ element has @alt@ attribute',
              PASS_2:   '@%1@ element has @aria-labelledby@ attribute',
              PASS_3:   '@%1@ element has @aria-label@ attribute',
              PASS_4:   '@%1@ element has @title@ attribute',
              ACTION_1: 'Add a @alt@, @aria-labelledby@ or @aria-label@ attribute to the @%1@ element',
              ACTION_2: 'Use @aria-labelledby@ or @aria-label@ attribute instead of @alt@ attribute for the accessible name for @%1@ element with @role="img"@',
              HIDDEN_1: 'Image has text alternative was not tested becasue the @%1@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Accessible name provides a description of an image for people who cannot see the image, usually the accessible name comes from an alt attribute',                   
              'Accessible name that is an empty string is ignored by assistive technologies and indicates an image is being used for styling rather than meaningful content'                   
            ],
            TECHNIQUES: [
              'Text alternatives should describe the purpose of images as succinctly as possible (e.g. usually less than ~100 characters)',
              'The @alt@ attribute is the preferred and most commonly used way to provide a text alternative for @img@ and @area@ elements',
              'The @aria-labelledby@ attribute can be used to provide a text alternative when images can be described using text associated with the image, or for elements using @role="img"@', 
              'The @aria-label@ attribute should only be used to provide a text alternative in special cases when an element has @role="img"@ attribute', 
              'The @title@ attribute will be used to provide a text alternative if no other techniques is found', 
              'If an image is purely stylistic or decorative set the text alternative must result in an empty string (i.e. @alt=""@) or use @role="presentation"@'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 13.8 How to specify alternate text', 
                url:   'http://www.w3.org/TR/html4/struct/objects.html#adef-alt'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G94: Providing short text alternative for non-text content that serves the same purpose and presents the same information as the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G94'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G95: Providing short text alternatives that provide a brief description of the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G95'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Text Equivalents for Images and other Non-Text Objects Best Practices', 
                url:   'http://html.cita.illinois.edu/text/'
              }                            
            ]
        },
        IMAGE_2: {
            ID:                    'Image Rule 2',
            DEFINITION:            'The @longdesc@ attribute %s have a valid URL',
            SUMMARY:               '@longdesc@ %s be valid',
            TARGET_RESOURCES_DESC: '@img@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Change @longdesc@ attribute to a valid URL',
              ACTION_FAIL_P:   'Change the %N_F out of %N_T images with a @longdesc@ attribute to a valid URL',
              ACTION_MC_S:     'Verify @longdesc@ attribute is a valid URL',
              ACTION_MC_P:     'Verify the %N_F @longdesc@ attributes are valid URLs',
              NOT_APPLICABLE:  'No @img@ elements with a @longdesc@ attribute on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:          '@longdesc@ attribute is a valid URI',
              ACTION_1:        'Change @longdesc@ attribute to a valid URI',
              MANUAL_CHECK_1:  'Use a browser to test if the @longdesc@ attribute is a valid URL',
              HIDDEN_1:        '@longdesc@ has valid url was not tested becasue @img@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Some images (i.e charts, bar graphs, organizational charts, complex pictures and images) need a longer text equivalent than can be provided with the alt text content'
            ],
            TECHNIQUES: [
              '@longdesc@ can be used to provide an internal link or external link to information that provides a extended and more detailed text equivalent of the image'
            ],
            MANUAL_CHECKS: [
              'Use browser inspection features to identify the value of the longdesc @attribute@, try using the value to open a web page in the browser'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: longdesc attribute', 
                url:   'http://www.w3.org/TR/html4/struct/objects.html#adef-longdesc-IMG'
              },
              { type: OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE,
                title: 'H45: Using longdesc',
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H45'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G92: Providing long description for non-text content that serves the same purpose and presents ', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G92'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G74: Providing a long description in text near the non-text content, with a reference to the location of the long description in the short description', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G74'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G73: Providing a long description in another location with a link to it that is immediately adjacent to the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G73'
              },
              { type: OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE,
                title: 'H45: Using longdesc',
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H45'
              },  
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Text Equivalents for Images and other Non-Text Objects Best Practices', 
                url:   'http://html.cita.illinois.edu/text/'
              }                            
            ]
        },
        IMAGE_3: {
            ID:                    'Image Rule 3',
            DEFINITION:            'The file name of the image %s not be part of the text alternative',
            SUMMARY:               'Don\'t use filename',
            TARGET_RESOURCES_DESC: '@img@, @area@ and @[role="img"]@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Change the value of the @alt@ attribute of the image element to describe the image and not use the image file name',
              ACTION_FAIL_P:   'Change the value of the @alt@ attribute of the %N_F out of %N_T images to NOT use the image and not use the image file name',
              NOT_APPLICABLE:  'No @img@ elements on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'text alternative does not contain the filename',
              ACTION_1: 'Change text alternative to succinctly describe the purpose and/or content of the image',
              HIDDEN_1: 'The file name must not be part of the text alternative was not tested because the @img@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Text alternatives provide a description of images for people who cannot see the image and the file name is not useful information',                   
              'Empty text alternatives are ignored by assistive technologies and indicates an image is being used for styling rather than meaningful content'                   
            ],
            TECHNIQUES: [
              'Text alternatives should describe the purpose of images as succinctly as possible (e.g. usually less than ~100 characters) and do not include the file name as part of the text alternative',
              'The @alt@ attribute is the preferred and most commonly used way to provide a text alternative for @img@ and @area@ elements',
              'The @aria-labelledby@ attribute can be used to provide a text alternative when images can be described using visible captions associated with the image', 
              'The @aria-label@ attribute should only be used to provide a text alternative in special cases when an element has @role="img"@ attribute', 
              'The @title@ attribute will be used to provide a text alternative if none of the other techniques is found', 
              'If an image is purely stylistic or decorative set the text alternative must result in an empty string (i.e. @alt=""@) or use @role="presentation"@'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 13.8 How to specify alternate text', 
                url:   'http://www.w3.org/TR/html4/struct/objects.html#adef-alt'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G94: Providing short text alternative for non-text content that serves the same purpose and presents the same information as the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G94'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G95: Providing short text alternatives that provide a brief description of the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G95'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F30: Failure of Success Criterion 1.1.1 and 1.2.1 due to using text alternatives that are not alternatives (e.g., filenames or placeholder text)', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F30'
              }                            
            ]
        },
        IMAGE_4_EN: {
            ID:                    'Image Rule 4 (English)',
            DEFINITION:            'Text alternatives %s less than 100 characters',
            SUMMARY:               'Text alternative length',
            TARGET_RESOURCES_DESC: '@img@, @area@ and @[role="img"]@',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'image element text alternative is GREATER than 100 characters',
              ACTION_MC_P:    'All %N_F image elements have text alternatives GREATER than 100 characters',
              NOT_APPLICABLE: 'No @img@, @area@ or @[role="img"]@ elements on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'Text alternative (e.g. typically @alt@ attribute) is less than 100 characters',
              ACTION_1:       'Add text alternative (e.g. typically @alt@ attribute) to the image',
              MANUAL_CHECK_1: 'Check the text alternative (e.g. typically @alt@ attribute) to see if it can be reworded to be less less than 100 characters, also consider using a long description texhnique (i.e. @longdesc@ or @aria-describedby@) to provide a longer description and allow the text alternative to be shorter',
              HIDDEN_1:       'Length of text alternative eas not tested because the @img@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Text alternatives provides a description of the image for people who cannot see the image',                   
              'Long text alternatives can reduce usability by increasing the time it takes to read a web page and understand the purpose of an image in the web site', 
              'Alt text that is an empty string is ignored by assistive technologies and indicates an image is being used for styling rather than meaningful content' 
            ],
            TECHNIQUES: [
              'Text alternatives should describe the purpose of images as succinctly as possible (e.g. usually less than ~100 characters) and do not include the file name as part of the text alternative',
              'The @alt@ attribute is the preferred and most commonly used way to provide a text alternative for @img@ and @area@ elements',
              'The @aria-labelledby@ attribute can be used to provide a text alternative when images can be described using visible captions associated with the image', 
              'The @aria-label@ attribute should only be used to provide a text alternative in special cases when an element has @role="img"@ attribute', 
              'The @title@ attribute will be used to provide a text alternative if none of the other techniques is found', 
              'If an image is purely stylistic or decorative set the text alternative must result in an empty string (i.e. @alt=""@) or use @role="presentation"@'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 13.8 How to specify alternate text', 
                url:   'http://www.w3.org/TR/html4/struct/objects.html#adef-alt'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G94: Providing short text alternative for non-text content that serves the same purpose and presents the same information as the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G94'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G95: Providing short text alternatives that provide a brief description of the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G95'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F30: Failure of Success Criterion 1.1.1 and 1.2.1 due to using text alternatives that are not alternatives (e.g., filenames or placeholder text)', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F30'
              }                            
            ]
        },
        IMAGE_5: {
            ID:                    'Image Rule 5',
            DEFINITION:            'If an image element has a height or width less than 6 pixels or its alt text set to empty, the image %s set its role attribute to "presentation" or the image %s be removed and CSS %s should be used for positioning.',
            SUMMARY:               'Small/decorative images set to presentation',
            TARGET_RESOURCES_DESC: '@img@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'For the one small image in the page, add a @role=presentation@ or change text alternative to an empty string (e.g. @alt=""@)',
              ACTION_FAIL_P:   'For the %N_F small images on the page, add a @role=presentation@ or change text alternative to an empty string (e.g. @alt=""@)',
              NOT_APPLICABLE:  'No small or decorative images (i.e. less than 6 pixels high or wide) on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'Image element has @role="presentation"@',
              PASS_2:   'Image element has @alt=""@',
              ACTION_1: 'Add @role="presentation"@ or change text alternative to empty string (i.e. @alt=""@)',
              HIDDEN_1: 'Small images set to @role="presentation"@ was not tested becasue the @img@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Small and decorative images (i.e. less than 6 pixels high or wide) can be ignored by assistive technologies',
              'Images with the @alt=""@ attribute should be set to the empty string'                   
            ],
            TECHNIQUES: [
              'Small images are usually purely stylistic or decorative and the @alt@ text content should be the empty string (i.e. @alt=""@)'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 13.8 How to specify alternate text', 
                url:   'http://www.w3.org/TR/html4/struct/objects.html#adef-alt'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F30: Failure of Success Criterion 1.1.1 and 1.2.1 due to using text alternatives that are not alternatives (e.g., filenames or placeholder text)', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F30'
              }                            
            ]
        },
        IMAGE_6: {
            ID:                    'Image Rule 6',
            DEFINITION:            'Verify image element is be used for styling or decoration',
            SUMMARY:               'Verify decorative image',
            TARGET_RESOURCES_DESC: '@img@',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify @img@ element with @alt=""@ or @role="presentation"@ is purely decorative',
              ACTION_MC_P:    'Verify %N_MC out of %N_T @img@ elements with @alt=""@ or @role="presentation"@ are purely decorative',
              NOT_APPLICABLE: 'No @img@ elements identified as decorative (i.e. @alt=""@ or @role="presentation"@) on this page'                                          
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: 'Verify the image is only used for styling or decoration',
              HIDDEN_1:       'Verify decorative image was not tested because @img@ element is hidden from assistive technologies using CSS'
            },  
            PURPOSE: [
              'If an image is purely decoration or used for styling users of screen readers do not need to know the image exists',                   
              'If an image does not have alt text content and contains information, users of assistive technology will not have access to the information'                   
            ],
            TECHNIQUES: [
              'Use the attributes @alt=""@ or @role="presentation"@ to indicate an image is used purely for stylistic or decorative purposes'
            ],
            MANUAL_CHECKS: [
              'Find each image on the page and check to make sure is only being used decoratively or is redudent with other information on the page'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 13.8 How to specify alternate text', 
                url:   'http://www.w3.org/TR/html4/struct/objects.html#adef-alt'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G94: Providing short text alternative for non-text content that serves the same purpose and presents the same information as the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G94'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G95: Providing short text alternatives that provide a brief description of the non-text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G95'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'iCITA Best Practices: Text Equivalents for Images and other Non-Text Objects Best Practices', 
                url:   'http://html.cita.illinois.edu/text/'
              }                            
            ]
        },
     KEYBOARD_1: {
            ID:                    'Keyboard Rule 1',
            DEFINITION:            'Widget elements %s have keyboard event handlers',
            SUMMARY:               'Widgets %s support keyboard',
            TARGET_RESOURCES_DESC: 'Widget elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify the widget element has keyboard event handlers through is parent elements, owner widget (i.e. aria-activedescendant) or required child widgets',
              ACTION_MC_P:    'Verify the %N_MC out %N_T widgets have keyboard event handlers through is parent elements, owner widget (i.e. aria-activedescendant) or required child widgets',
              ACTION_FAIL_S:  'Add event handlers to the widget to support keyboard operation of the widget',
              ACTION_FAIL_P:  'Add event handlers to the %N_F out of %N_T widgets to support keyboard operation of each widget',
              NOT_APPLICABLE: 'No widgets on the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:               '@%1@ widget has keyboard support through event handlers on the widget element',
              MANUAL_CHECK_1:       'Verify the @%1@ widget has keyboard support through keyboard event handlers on a parent widget element',
              MANUAL_CHECK_2:       'Verify the @%1@ widget has keyboard support through keyboard event handlers through @aria-activedescendant@ support on its @%2@ owner widget',
              MANUAL_CHECK_3: 'Verify the @%1@ widget has keyboard support through keyboard event handlers on a required child widgets',
              ACTION_1:       'Add keyboard event handlers to the @%1@ widget to support keyboard interaction with the widget',
              HIDDEN_1:       'Widgets must support keyboard interaction was not tested because @%1@ widget is hidden from assistive technologies'
            },
            PURPOSE: [
              'Keyboard support is required by people who cannot use the mouse to interact with a widget'                   
            ],
            TECHNIQUES: [
              'Use the @keyup@, @keydown@ and @keypress@ events to support keyboard interaction with widgets'
            ],
            MANUAL_CHECKS: [
              'Try using the web application with only the keyboard, and verify that all functionalities can be performed with the keyboard alone',
              'For widgets check the keyboard behavior with the recommendations in the ARIA Authoring Practices guide to make sure that keyboard operation is predictable to the user'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              }                            
            ]
        },
     KEYBOARD_2: {
            ID:                    'Keyboard Rule 2',
            DEFINITION:            'Widget elements %s use tabindex to add keyboard focus support on non-interactive elements',
            SUMMARY:               'Tabindex for focus',
            TARGET_RESOURCES_DESC: 'Widget elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @tabindex@ value to the widget or add @aria-activedescendant@ support to an ancestor widget to provide keyboard focus support to the widget',
              ACTION_FAIL_P:   'Add @tabindex@ value to the widget or add @aria-activedescendant@ support to an ancestor widget to provide keyboard focus support to the %N_F out of %N_T widgets',
              ACTION_MC_S:     'Verify that the @aria-activedescendant@ provides keyboard focus support to the widget that is a child of a widget with an @aria-activedescendent@ property',
              ACTION_MC_P:     'Verify that the @aria-activedescendant@ provides keyboard focus support to the %N_MC out of %N_T widgets that are children of a widget with an @aria-activedescendent@ property',
              NOT_APPLICABLE:  'No widgets on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:          '@%1@ widget is on a @%2@ element that can receive keyboard focus',
              PASS_2:          '@%1@ widget has a @tabindex@ value that supports receiving keyboard focus',
              MANUAL_CHECK_1:  '@%1@ widget is the child of a widget that has an @aria-activedescendant@ attribute, verify the @aria-activedescendant@ supports references to the @id=%2@ of this widget',
              MANUAL_CHECK_2:  'Verify the child widgets of the @%1@ element with an onClick event of accurately represent the interactive features of this section of the web page',
              ACTION_1:        'Add @tabindex@ value to the widget or add @aria-activedescendant@ support to an ancestor widget to provide keyboard focus support',
              HIDDEN_1:        '@tabindex@ to support focus events was not tested becayse @%1@ widget is hidden from assistive technology'
            },
            PURPOSE: [
              'Keyboard support is required by people who cannot use the mouse to interact with a widget'                   
            ],
            TECHNIQUES: [
              'The @tabindex@ enables non-interactive elements (i.e. @div@, @li@, @span@ ...) to receive and process focus ',
              'The @tabindex@ enables non-interactive elements (i.e. @div@, @li@, @span@ ...) to receive and process focus '
            ],
            MANUAL_CHECKS: [
              'Using only the keyboard verify all features of a widget can be identified by assistive technology by changing the keyboard focus'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              }                            
            ]
        },
     KEYBOARD_3: {
            ID:                    'Keyboard Rule 3',
            DEFINITION:            'All functionality %s be operable through the keyboard alone',
            SUMMARY:               'Keyboard operation',
            TARGET_RESOURCES_DESC: 'Widgets, @object@ and @applet@ elements must support keyboard operation',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:     'Check the widget or embedded application to make sure all functionalities are available through the keyboard',
              ACTION_MC_P:     'Check the %N_PG widgets and/or embedded applications to make sure all functionalities are available through the keyboard',
              NOT_APPLICABLE:  'No @object@, @applet@ or widget elements on the page'              
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1:  'Check the %1 widgets on the page for keyboard support for all functionalities',
              MANUAL_CHECK_2:  'Check the %1 widgets on the page for keyboard support for all functionalities',
              MANUAL_CHECK_3:  'Check the %1 widgets and %3 @object@s on the page for keyboard support for all functionalities',
              PAGE_1:          'The @%1@ element with @role="%2"@ is part of the manual check for keyboard support for all functionalities',
              PAGE_2:          'The @%1@ element is part of the manual check for keyboard support for all functionalities',
              HIDDEN_1:        'The @%1@ element with @role="%2"@ is hidden and therefore NOT a part of the manual check for keyboard support for all functionalities',
              HIDDEN_2:        'The @%1@ element is hidden, and therefore NOT a part of the manual check for keyboard support for all functionalities'
            },
            PURPOSE: [
              'Many people cannot use the mouse, either because they cannot see the pointer or to not have fine motor skills to accurately position the mouse',
              'This requirement does not mean not to support the mouses, but just to make sure their is an equivalent way using the keyboard for all tasks that can be done with the mouse',
              'Using efficient and plateform conventions for keyboard support will make it it easier for all users to benefit from the keyboard support, since the keys will be eaiser to discover and familair to users',
              'Touch typists often prefer keyboard commands than the mouse for performing operations, making them much more efficient'
            ],
            TECHNIQUES: [
              'Use the WAI-ARIA 1.0 Authoring Practices to determine the keyboard support for widgets',
              'Use keyboard event handers to implement keyboard support'
            ],
            MANUAL_CHECKS: [
              'Make a list of the functional feature of a web site.  NOTE: if a page only has simple links and form controls then keyboard support is built-in through browser support',
              'Using only the keyboard excercise all the functionalities of the web page'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'WAI-ARIA 1.0 Authoring Practices', 
                url:   'http://www.w3.org/WAI/PF/aria-practices/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/'
              }                            
            ]
        },
     KEYBOARD_4: {
            ID:                    'Keyboard Rule 4',
            DEFINITION:            '@object@ and @applet@ elements % not trap the keyboard',
            SUMMARY:               'No keyboard trap',
            TARGET_RESOURCES_DESC: '@object@ and @applet@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:     'Check the embedded application to make sure the application does not trap the keyboard',
              ACTION_MC_P:     'Check the %N_MC embedded applications to make sure application does not trap the keyboard',
              NOT_APPLICABLE:  'No @applet@ and @object@ elements on the page'              
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1:  'Check the %1 element to see if it traps the keyboard',
              HIDDEN_1:        '%1 element is hidden, so it cannot trap the keyboard'
            },
            PURPOSE: [
              'If an embedded application (i.e. @object@ or @applet@ element) traps the keyboard, keyboard users will not be able to use the web page'                   
            ],
            TECHNIQUES: [
              'Use @tabindex="-1"@ on the element to remove it from "tab" order of the page',
              'If the embedded application does support accessibility, use a button to move focus to the application'
            ],
            MANUAL_CHECKS: [
              'Move keyboard focus to the embedded application and see if you can move focus back to the web content using just the keyboard'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              }                            
            ]
        },    
        LANDMARK_1: {
            ID:                    'Landmark Rule 1',
            DEFINITION:            'Each page %s contain at least one @main@ landmark',
            SUMMARY:               'Page %s have @main@ landmark',
            TARGET_RESOURCES_DESC: '@main@ landmark',
            RULE_RESULT_MESSAGES: {
              PAGE_FAIL_S:   'Page does NOT contain a @main@ landmark',
              PAGE_FAIL_P:   'Page does NOT contain a @main@ landmark'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    'Page contains one @main@ landmark',
              PASS_2:    'Page contains %1 @main@ landmarks',
              ACTION_1:  'Add a @main@ landmark to the page, the @main@ landmark must contain the main content of the page',
              HIDDEN_1:  '@main@ landmark is hidden from assistive technologies and does not contribute to satisfying the @main@ landmark rule'
            },  
            PURPOSE: [
              'Main landmarks provide a navigation point for users of assistive technologies to the main content of the page'                   
            ],
            TECHNIQUES: [
              'Include an @role="main"@ attribute on the element that contains the main content',
              'Use the aria-labelledby or aria-label to describe the content of the main landmark'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: main role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#main'
              }                            
            ]
        },
        LANDMARK_2: {
            ID:                    'Landmark Rule 2',
            DEFINITION:            'All rendered content %s be contained within a landmark',
            SUMMARY:               'Content %s be within landmark',
            TARGET_RESOURCES_DESC: 'all rendered content',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Update the landmark structure of the page and place the element currently outside of a landmark into its proper landmark container',
              ACTION_FAIL_P:   'update the landmark structure of the page and place the %N_F out of %N_T elements currently outside of landmarks into their proper landmark container',
              ACTION_MC_S:     'Verify if the element contains renderable content.  If it does contain renderable content change the landmark structure to make sure it is contained in a landmark',
              ACTION_MC_P:     'Verify if the %N_MC elements contain renderable contennt.  If any of the %N_MC elements contain renderable content change the landmark structure to make sure they are contained in a landmark',
              NOT_APPLICABLE:  'This rule is not evaluated when landmarks are not present in the web page.  Consider adding landmarks to the page to improve navigation and orientation.'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:          '@%1@ landmark contains %2 elements with content',
              MANUAL_CHECK_1:  '@%1@ element may contain renderable content, if so move it into an appropriate landmark',
              MANUAL_CHECK_2:  '@%1@ landmark does not contain content, check the landmark structure of the page to make sure this landmark provides useful structural information, if not remove the landmark',
              ACTION_1:        'Move @%1@ element into an appropriate landmark',
              HIDDEN_1:        'All content must be in a landmark rule was not tested because the @%1@ element is hidden from assistive technology'
            },  
            PURPOSE: [
              'Landmarks provide a way to organize content of a page to users of assistive technology, similar to visual and interaction designers organize information for people using a graphical rendering of the content.'                   
            ],
            TECHNIQUES: [
              'Use the appropriate landmarks to identify the different sections of a web page',
              'The most important landmarks are the @main@ and @navigation@ landmarks since they will be the most used'
            ],
            MANUAL_CHECKS: [
              '@object@, @embed@ and @applet@ tags may be used to render content, use inspection tools to identify if any of these elements actually render content on the page.'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Landmark Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#landmark_roles'
              }                            
            ]
        },    
        LANDMARK_3: {
            ID:                    'Landmark Rule 3',
            DEFINITION:            'Each page %s contain at least one @navigation@ landmark',
            SUMMARY:               'Page %s have @navigation@ landmark',
            TARGET_RESOURCES_DESC: '@navigation@ landmark',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add a @navigation@ landmark(s) that identifies the group(s) of navigation links on a page',
              ACTION_MC_S:   'Verify that the page has a group or groups of navigation links.  If a group or groups of navigation links are idenfied, use the @navigation@ landmark to identify each group'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'Page contains an %1 elements with @role="navigation"@',
              ACTION_1:       'Add a @navigation@ landmark to the page, the navigation landmark must identify the sets navigation lists on the page',
              MANUAL_CHECK_1: 'Verify that the page has a group or groups of navigation links.  If a group or groups of navigation links are idenfied, use the @navigation@ landmark to identify each group',
              HIDDEN_1:       '@navigation@ landmark is hidden from assistive technologies and does not contribute to satisfying the at least one @navigation@ landmark rule'
            },  
            PURPOSE: [
              'Navigation landmarks provide a way to identify groups of links with in a web page'                   
            ],
            TECHNIQUES: [
              'Include an @role="navigation"@ attribute on a element that contains @ol@ and @ul@ elements that contain li elements with links',
              'Use the aria-labelledby or aria-label to describe the purpose of the links (i.e. table on contents of a page, site map...)'
            ],
            MANUAL_CHECKS: [
              'List of links that are used for navigation with or between pages in your website should use @navigation@ landmarks'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: navigation role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#navigation'
              }                            
            ]
        },    
        LANDMARK_4: {
            ID:                    'Landmark Rule 4',
            DEFINITION:            'Each page %s contain only one @banner@ landmark',
            SUMMARY:               'One @banner@ landmark',
            TARGET_RESOURCES_DESC: '@banner@ landmark',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'More than one @banner@ landmark on the page, only one @banner@ landmark is allowed per page or frame',
              ACTION_FAIL_P:  'More than one @banner@ landmark on the page, only one @banner@ landmark is allowed per page or frame',
              ACTION_MC_S:    'Verify that the page has a branding banner at the top of the page.  If the page has a branding banner use the @banner@ landmark to identify the content that is part of the banner'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:      'Page contains one element with @role="banner"@',
              ACTION_1:    'Review the @banner@ landmarks on the page and modify the page to include only one element with @role="banner"@',
              ACTION_MC_S: 'Verify that the page has a branding banner at the top of the page.  If the page has a branding banner use @role="banner"@ on the container element to identify the content that is part of the banner',
              HIDDEN_1:    '@banner@ landmark is hidden from assistive technologies and does not contribute to satisfying the at least one @banner@ landmark rule'
            },  
            PURPOSE: [
              'Banner landmarks provide a way to identify branding content usually at the top of a web page'                   
            ],
            TECHNIQUES: [
              'Include an @role="banner"@ attribute on a element that contains the branding content at the top of a page',
              'Pages typically one have one @banner@ landmark',
              'In websites that support mashups using @iframe@ or @frame@ elements, a @banner@ landmark is allowed in each frame',
              'If the page is part of a website supporting mashups, use the @aria-labelledby@ or @aria-label@ to differentiate possible @banner@ landmarks in each frame'
            ],
            MANUAL_CHECKS: [
              'Banners are a convention used on most web sites to provide branding and often used as a location for advertising information'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: banner role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#banner'
              }                            
            ]
        },    
        LANDMARK_5: {
            ID:                    'Landmark Rule 5',
            DEFINITION:            'Each page %s contain only one @contentinfo@ landmark',
            SUMMARY:               'One @contentinfo@ landmark',
            TARGET_RESOURCES_DESC: '@contentinfo@ landmark',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'More than one element with the @role="contentinfo"@, only one @contentinfo@ landmark is allowed per page or frame',
              ACTION_FAIL_P: 'More than one element with the @role="contentinfo"@, only one @contentinfo@ landmark is allowed per page or frame',
              ACTION_MC_S:   'Verify that the page has footer information (i.e. copyright, privacy, contact information, ....) at the end of the page.  If the page has a footer information use the @contentinfo@ landmark to identify the content that is part of the footer'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'Page contains a one element with @role="contentinfo"@',
              ACTION_1:       'Review the @contentinfo@ landmarks on the page and modify the page to include only one @contentinfo@ landmark',
              MANUAL_CHECK_1: 'Verify that the page has footer information (i.e. copyright, privacy, contact information, ....) at the end of the page.  If the page has a footer information use the @contentinfo@ landmark to identify the content that is part of the footer',
              HIDDEN_1:       '@contentinfo@ landmark is hidden from assistive technologies and does not contribute to satisfying the at least one @contentinfo@ landmark rule'
            },  
            PURPOSE: [
              '@contentinfo@ landmarks provide a way to identify content found typically on the bottom of each page in a website, oftern referred to as a footer in publishing contexts',
              'The @contentinfo@ usually includes links like copyright information, privacy, and other general links found on all pages in the website.'
            ],
            TECHNIQUES: [
              'Include an @role="contentinfo"@ attribute on the contianer element for the content identified',
              'In websites that support mashups using @iframe@ or @frame@ elements, a @contentinfo@ landmark is allowed in each frame',
              'If the page is part of a website supporting mashups, use the @aria-labelledby@ or @aria-label@ to differentiate possible @contentinfo@ landmarks in each frame'
            ],
            MANUAL_CHECKS: [
              'Footers are a convention used on most web sites to provide copyright, contact, privacy and other types of adminstrative information',
              'Use @contentinfo@ to identify footer information on a web page'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: contentinfo role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#contentinfo'
              }                            
            ]
        },    
        LANDMARK_6: {
            ID:                    'Landmark Rule 6',
            DEFINITION:            'Headings %s be properly nested within a landmark',
            SUMMARY:               'Headings nested in landmarks',
            TARGET_RESOURCES_DESC: 'Landmark elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Review the heading structure within the landmark and adjust the headings to be properly nested',
              ACTION_FAIL_P:  'Review the heading structure for each landmark with more than one heading, and adjust the headings in each landmark to be properly nested',
              NOT_APPLICABLE: 'No headings or only one heading in each landmark; or no landmarks on the page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     'The %1 headings within the @%2@ landmark are properly nested',
              ACTION_1:   '%1 of the %2 headings in the @%3@ landmark are not properly nested, adjust the headings to make sure they are properly nested',
              ACTION_2:   '@%1@ heading is not properly nested in the @%2@ landmark, adjust the headings to make sure they are properly nested',
              ACTION_3:   '@%1@ heading in the @%2@ landmark does not have content, add content or remove the heading',
              HIDDEN_1:   'Headings properly nested within landmark was not tested because @%1@ landmark is hidden from assistive technology'
            },  
            PURPOSE: [
              'contentinfo landmarks provide a way to identify content found typically on the bottom of each page in a website',
              'The contentinfo usually includes links like copyright information, privacy, and other general links support all pages in the website.'
            ],
            TECHNIQUES: [
              'Include an @role="contentinfo"@ attribute on a element that contains @ol@ and @ul@ elements that contain li elements with links',
              'Use the aria-labelledby or aria-label to describe the purpose of the links (i.e. table on contents of a page, site map...)'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: contentinfo role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#contentinfo'
              }                            
            ]
        },
        LAYOUT_1: {
            ID:                    'Layout Rule 1',
            DEFINITION:            'Web pages with layout tables %s provide content in a meaningful sequence',
            SUMMARY:               'Page %s have meaningful sequence',
            TARGET_RESOURCES_DESC: '@table@ elements used for layout',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify document has a meaningful sequence when layout table markup is disabled.  If content does not have a meaningful sequence, reorganize content on the page to have a meaningful sequence when layout tables are disabled.',   
              NOT_APPLICABLE: 'No layout tables found on this page'             
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:          'Table is one column wide, and will have the same document sequence when table markup is disabled',
              MANUAL_CHECK_1:  'Verify document has a meaningful sequence of content when layout table markup is disabled',
              MANUAL_CHECK_2:  'Verify the content in the %1x%2 layout table has a meaningful sequence of content when table markup is disabled , if the table is actually a data table add data table markup to give the table an effective caption and the data tables headings',
              MANUAL_CHECK_3:  'Verify the nesting of tables for layout of content maintains a meaningful sequence of content when table markup is disabled',
              HIDDEN_1:        'Meaningful sequence was not tested The layout @table@ is hidden from assistive technologies'
            },  
            PURPOSE: [
              'The sequence of content (i.e. order) in the document code affects its meaning, especially for users of assistive technology who cannot see the visual cues provided in a graphical that provide information about the relationships between content'                   
            ],
            TECHNIQUES: [
              'Use CSS and web standards techniques for the coding of content, and the graphical styling and positioning of content',
              'Avoid using table markup for graphical layout, if you do use tables for layout make sure the content still is meaningful when the table markup is disabled',
              'Avoid using nested tables for layout, the deeper the level of nesting the more chance there of having a confusing sequence of content',
              'Tables that are used for layout should use only @tr@ and @td@ elements, and the @table@, @tr@ and @td@ elements should have a @role="presentation"@ attribute to clearly indicate the table markup is being used for layout'
            ],
            MANUAL_CHECKS: [
              'Use browser developer tools to disable table markup or enable a user stylesheet to change table cells to be rendered as block level elements',
              'With layout tables disabled, view the content to make sure the reading order and structure of the document makes sense'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification: Visual formatting model', 
                url:   'http://www.w3.org/TR/CSS21/visuren.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G57: Ordering the content in a meaningful sequence', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G57'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'C6: Positioning content based on structural markup', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/C6'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'C8: Using CSS letter-spacing to control spacing within a word', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/C8'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'C27: Making the DOM order match the visual order', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/C27'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F1: Failure of Success Criterion 1.3.2 due to changing the meaning of content by positioning information with CSS', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F1'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F33: Failure of Success Criterion 1.3.1 and 1.3.2 due to using white space characters to create multiple columns in plain text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F33'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F34: Failure of Success Criterion 1.3.1 and 1.3.2 due to using white space characters to format tables in plain text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F34'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F49: Failure of Success Criterion 1.3.2 due to using an HTML layout table that does not make sense when linearized', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F49'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.OTHER, 
                title: 'Web Standards Group', 
                url:   'http://webstandardsgroup.org/standards/'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.OTHER, 
                title: 'W3C Standards', 
                url:   'http://www.w3.org/standards/'
              }
            ]   
        },
        LAYOUT_2: {
            ID:                    'Layout Rule 2',
            DEFINITION:            'Tables %s not be nested for layout of content',
            SUMMARY:               'Do not nest layout tables',
            TARGET_RESOURCES_DESC: '@table@ elements used for layout',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Update the markup and CSS on this page to remove the nesting of the layout table that is nested',
              ACTION_FAIL_P:   'Update the markup and CSS on this page to remove the nesting of $N_F layout tables that are nested',
              NOT_APPLICABLE:  'No table elements used for layout'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    'Table is not nested with another layout table',
              PASS_2:    'Table is one column wide, and will have the same document sequence when table markup is disabled',
              ACTION_1:  'Update the markup and CSS on this page to remove the nesting of this layout table',
              HIDDEN_1:  'Table nesting was not tested beacuse the @table@ is hidden from assistive technology'
            },  
            PURPOSE: [
              'The sequence of content (i.e. order) in the document code affects its meaning, nesting layout tables often makes the sequence of content less understandable'                   
            ],
            TECHNIQUES: [
              'Use CSS and web standards techniques for the coding of content, and the graphical styling and positioning of content',
              'Avoid using table markup for graphical layout, if you do use tables for layout make sure the content still is meaningful when the table markup is disabled',
              'Avoid using nested tables for layout, the deeper the level of nesting the more chance there of having a confusing sequence of content',
              'Tables that are used for layout should use only @tr@ and @td@ elements, and the @table@, @tr@ and @td@ elements should have a @role="presentation"@ attribute to clearly indicate the table markup is being used for layout'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification: Visual formatting model', 
                url:   'http://www.w3.org/TR/CSS21/visuren.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F33: Failure of Success Criterion 1.3.1 and 1.3.2 due to using white space characters to create multiple columns in plain text content', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F33'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F49: Failure of Success Criterion 1.3.2 due to using an HTML layout table that does not make sense when linearized', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F49'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.OTHER, 
                title: 'Web Standards Group', 
                url:   'http://webstandardsgroup.org/standards/'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.OTHER, 
                title: 'W3C Standards', 
                url:   'http://www.w3.org/standards/'
              }
            ]   
        },
        LAYOUT_3: {
            ID:                    'Layout Rule 3',
            DEFINITION:            'Layout tables %s use @role="presentation"@ on all table elements',
            SUMMARY:               'Layout tables use @role="presentation"@',
            TARGET_RESOURCES_DESC: '@table@ elements used for layout',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @role="presentation"@ to table element (i.e. @table@, @tr@, @td@ elements) used in the layout table',
              ACTION_FAIL_P:   'Add @role="presentation"@ to $N_F table elements (i.e. @table@, @tr@, @td@ elements) used in layout tables',
              NOT_APPLICABLE:  'No table elements used for layout'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    '%1 element has @role="presentation"@',
              ACTION_1:  'Add @role="presentation"@ to the %1 element, if the table is actually a data table use appropriate data table markup',
              HIDDEN_1:  '@role="presentation"@ on layout table markup was not tested because the %1 element is hidden from assistive technologies'
            },  
            PURPOSE: [
              'Using @role="presentation"@ communicates assistive technologies that the table is being used for markup'                   
            ],
            TECHNIQUES: [
              'Use @role="presentation"@ on all table elements in a layout table to help assistive technology understand the table is being used for layout, rather than tabular data'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0: Presentation Role', 
                url:   'http://www.w3.org/TR/wai-aria/roles#presentation'
              },
              { type:  OpenAjax.a11y.REFERENCES.OTHER, 
                title: 'WAI-ARIA 1.0 Authoring Practices: Presentation Role', 
                url:   'http://www.w3.org/WAI/PF/aria-practices/#presentation_role'
              }
            ]   
        },
        LINK_1: {
            ID:                    'Link Rule 1',
            DEFINITION:            'Links with the same @href@ %s have the same link text',
            SUMMARY:               'Link text %s be consistent',
            TARGET_RESOURCES_DESC: '@a@ and @area@ elements and elements with @role="link"@',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_P:    'Verify all %N_MC @a@, @area@ or @[role=link]@ elements accessible name describes the target of each link, since there are two different accessible names to the same @href@ value',
              ACTION_FAIL_P:  'Change the accessible names to a consistent name for the links that share the same @href@s',
              NOT_APPLICABLE: 'No @a@, @area@ or @[role=link]@ elements on page that share the same @href@ value'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         '@%1@ element has the same accessible name as the %2 links it shares the same @href@ with',
              MANUAL_CHECK_1: 'Verify the @%1@ element has the an accessible name that makes sense to users, since the link shares the same @href@ with a link that has a different accessible name',
              ACTION_1:       'Change the text content of the @%1@ element to make it the same as other %2 links that share the same @href@ value',
              HIDDEN_1:       'Consistent link text for links sharing the same @href@ value was not tested because the @%1@ element is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'Consistency of accessible names for a link makes interaction with web pages more predictable'                   
            ],
            TECHNIQUES: [
              'Use the same text for links that point to the same URL',
              'Make sure the link text accurately describes the target of the link',
              'Use @aria-describedby@ to provide context for links that share the same accessible name, but have different @href@ values',
              'Use aria-'
            ],
            MANUAL_CHECKS: [
              'In general accessible names should be the same for links that share the same @href@ values.  In some cases you may have two accessible names to the same @href@ avlue, for example a links with the accessible names "Home" and "My Organization Name"'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 12.2 The A element', 
                url:   'http://www.w3.org/TR/html4/struct/links.html#edef-A'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H30: Providing link text that describes the purpose of a link for anchor elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H30'
              }  
            ]   
        },
        LINK_2: {
            ID:                    'Link Rule 2',
            DEFINITION:            'Links with different @href@s %s have unique accessible names',
            SUMMARY:               'Link text %s be unique',
            TARGET_RESOURCES_DESC: '@a@ and @area@ elements and elements with @role="link"@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_P:   'Change the accessible names to be uniue or add @aria-describedby@ attribute to the %N_F @a@, @area@ or @[role=link]@ elements to provide additional context to make the accessible names unique',
              NOT_APPLICABLE:  'No @a@, @area@ or @[role=link]@ elements on page share the same accessible name'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     '@%1@ element has the same @href@ value as the %2 links it shares a name with',
              ACTION_1:   'Change the accessible name of the @%1@ element or provide additional context text using @aria-describedby@ property to make the link text unique',
              HIDDEN_1:   'Unique accessible name for link was not tested because the @%1@ element is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'Links that point to different URLs should have unique accessible names, when a link shares the same accessible name but have different URLs, users using speech will be confused unless programmatic text context is provided'                   
            ],
            TECHNIQUES: [
              'The link text (i.e. accessible name) should uniquely describe the target of a link,',
              'Use @aria-labelledby@ to provide context for links that share the same link text'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 12.2 The A element', 
                url:   'http://www.w3.org/TR/html4/struct/links.html#edef-A'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H30: Providing link text that describes the purpose of a link for anchor elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H30'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Example 44 - Using aria-describedby to satisfy WCAG 2.4.4 Link Purpose in Context', 
                url:   'http://oaa-accessibility.org/example/44/'
              }  
            ]   
        },
        LINK_3: {
            ID:                    'Link Rule 3',
            DEFINITION:            'Link %s provide minimum target dimensions.',
            SUMMARY:               'Link %s not be small',
            TARGET_RESOURCES_DESC: '@a@ and @area@ elements and elements with @role="link"@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Increase the dimensions of the @a@, @area@ or @[role=link]@ element to be at least 12 pixels high and 12 pixels wide',
              ACTION_FAIL_P:   'Increase the dimensions of the %N_F @a@, @area@ or @[role=link]@ elements to be at least 12 pixels high and 12 pixels wide',
              ACTION_MC_S:     'Verify the dimensions of the @a@, @area@ or @[role=link]@ element to be at least 12 pixels high and 12 pixels wide, if not change the dimensions to meet the requirement',
              ACTION_MC_P:     'Verify the dimensions of the %N_F @a@, @area@ or @[role=link]@ elements to be at least 12 pixels high and 12 pixels wide, if not change the dimension to meet the requirement',
              NOT_APPLICABLE:  'No @a@, @area@ or @[role=link]@ elements on page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'The image link is more than 12 pixels high and 12 pixels wide',
              MANUAL_CHECK_1: 'The rendered dimensions of the @%1@ element could not be determined, verify the image link is at least 12 pixels high and 12 pixels wide',
              ACTION_1:       'The rendered dimensions of the @%1@ element is %2 pixels by %3 pixels, change the dimensions of the image to be at least 12 pixels high and 12 pixels wide',
              HIDDEN_1:       'Minimum demensions for links was not tested beacuse the @%1@ element is off screen or hidden'
            },  
            PURPOSE: [
              'Links must be large enough for people to see and select with the mouse'                   
            ],
            TECHNIQUES: [
              'Increase the size of the image to at least 12 pixels high and 12 pixels wide'
            ],
            MANUAL_CHECKS: [
              'Use visual inspection and browser development tools to determine if the image link is more than 12 pixels high and 12 pixels wide'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 12.2 The A element', 
                url:   'http://www.w3.org/TR/html4/struct/links.html#edef-A'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'IITAA Implementation Guidelines 1.0: 9.3 - Avoid using small links.', 
                url:   'http://www.dhs.state.il.us/IITAA/IITAAWebImplementationGuidelines.html'
              }                            
            ]
        },
        LINK_4: {
            ID:                    'Link Rule 4',
            DEFINITION:            'Accessible names for a link %s describe the target of the link',
            SUMMARY:               'Link text %s be descriptive',
            TARGET_RESOURCES_DESC: '@a@ and @area@ elements and elements with @role="link"@',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify the @a@, @area@ or @[role=link]@ elements accessible name describes the target of the link',
              ACTION_MC_P:    'Verify all %N_MC @a@, @area@ or @[role=link]@ elements have accessible names that describe the target of each link',
              ACTION_FAIL_S:  'Add text content to the link that does not have text content that describes the target of the link',
              ACTION_FAIL_P:  'Add text content to the %1 links that do not have text content that describes the target of each link',
              NOT_APPLICABLE: 'No @a@, @area@ or @[role=link]@ elements on the page'              
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1: '@%1@ element has the accessible name "%2", verify that the name accurately describes the target of the link, if not change the text content or text context of the link to create a more accessible name',
              MANUAL_CHECK_2: '@%1@ element has the accessible name "%2" with a text content of "%3", verify that the name and context text accurately describes the target of the link, if not change the text content or context of the link to create a more accessible name',
              ACTION_1:       'The @%1@ element does NOT have an accessible name, add text content to the link so the accessible name describes the target of the link',
              HIDDEN_1:       'Descriptive accessibe names for links was not tested because the @%1@ element is hidden from assistive technologies.'
            },  
            PURPOSE: [
              'Link text should describe the target of the link '                   
            ],
            TECHNIQUES: [
              'The text content of the link (i.e. the default accessible name) should uniquely describe the target of each link,',
              'Use @aria-label@ or @aria-labelledby@ to provide more descriptive accessible names when the text content of the link cannot be changed',
              'Use @aria-describedby@ to provide context for links that share the same link text but have some type of context to make the link text meaningful'
            ],
            MANUAL_CHECKS: [
              'Read the accessible name for each link aloud and make sure the accessible name describes the target of the link'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 12.2 The A element', 
                url:   'http://www.w3.org/TR/html4/struct/links.html#edef-A'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H30: Providing link text that describes the purpose of a link for anchor elements', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H30'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Example 44 - Using aria-describedby to satisfy WCAG 2.4.4 Link Purpose in Context', 
                url:   'http://oaa-accessibility.org/example/44/'
              }  
            ]   
        },
        TABLE_1: {
            ID:                    'Table Rule 1',
            DEFINITION:            'Data cells %s have header cells',
            SUMMARY:               'Data table %s have headers',
            TARGET_RESOURCES_DESC: '@td@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @th@ or @td[scope]@ elements to the first row or column of the data table or add a @headers@ attribute to define the headers for the data cell',
              ACTION_FAIL_P:   'Add @th@ or @td[scope]@ elements to the first row or column of the table or use the @headers@ attribute to define headers for the data cell',
              ACTION_MC_S:     'The @td@ element does not have any text content and it does not have any header cells, verify that this cell is being used for formatting and does not need headers',
              ACTION_MC_P:     'There are %N_F @td@ elements that do not have any text content and do not have any header cells, verify that thess cells are being used for formatting and do not need headers',
              NOT_APPLICABLE:  'No data tables in the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         'The @td@ element uses the @headers@ attribute with the following is: \'@1\' to define header cells',
              PASS_2:         'The @td@ element has at least one header cell in the row and/or column that contains the cell',
              ACTION_1:       'add header cells using the @headers@ attribute, since this table is a complex table',
              ACTION_2:       'add header cells using row and/or column @th@ elements, or the @headers@ attribute on the @td@ element',
              MANUAL_CHECK_1: 'The @td@ element does not have any text content and it does not have any header cells, verify that this cell is being used for formatting and does not need headers',
              HIDDEN_1:       'Header cells for data cells was not tested because the @td@ element is hidden from assistive technologies'
            },  
            PURPOSE: [
              'Data cells need header cells for people using speech to understand the content of the table cell, since they cannot see the visual relationships with header cells'                   
            ],
            TECHNIQUES: [
              'Use @th@ elements in the first row or column to identify row and column headers in a simple data tables',
              'Use @headers@ attribute on each @td@ element to identify header information in complex data tables',
              'Use @th@ element for cells used as header cells in the table',
              'While not recommended, it is valid to use @td@ element with a @scope@ attribute as header cell'
            ],
            MANUAL_CHECKS: [
              'Verify that empty @td@ and @th@ elements and does not need table headers'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 11.2.6 Table cells: The TH and TD elements', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#edef-TD'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: scope attribute', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#adef-scope'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H51: Using table markup to present tabular information', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H51'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H63: Using the scope attribute to associate header cells and data cells in data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H63'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist Checkpoint 1.3e: Tables', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webtableheaders.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Simple Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-simple.php'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Complex Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-complex.php'
              }  
            ]   
        },
        TABLE_2T: {
            ID:                    'Table Rule 2T (Transitional)',
            DEFINITION:            'Each data table %s have a effective caption with content',
            SUMMARY:               'Data tables %s have caption',
            TARGET_RESOURCES_DESC: '@caption@ and @table[summary]@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @caption@ element or @summary@ attribute to provide an effective caption',
              ACTION_FAIL_P:   'Add @caption@ element or @summary@ attribute to each of the %N_F out of %N_T data tables to provide an effective caption',
              NOT_APPLICABLE:  'No data tables on the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'Data table has an effective caption using the @caption@ with content: \'%1\'',
              PASS_2:   'Data table has an effective caption using the @summary@ attribute with content: \'%1\'',
              ACTION_1: 'Add @caption@ element or @summary@ attribute to provide an effective caption for the table, or if the table is really being used for layout or positioning add @role="presentation"@ to the @table@ element',
              HIDDEN_1: 'Effective caption for data table was not tested becasue the table is hidden from assistive technologies'
            },  
            PURPOSE: [
              'Effective caption of a table is important for people using speech, people with visual impairments and people with learning disabilities to help them understand the purpose of the table'                   
            ],
            TECHNIQUES: [
              'Use @caption@ element to provide an effective label',
              'Use @summary@ attribute to provide an effective label',
              'If the table is not used for tabular data, but instead for layout of content, use the @role="presentation"@ on all the table elements'
            ],
            MANUAL_CHECKS: [
              'Make sure the content of the effective label accurately and succinctly describes the purpose of the data table'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 11.2.2 Table Captions: The CAPTION element', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#h-11.2.2'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: summary attribute', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#adef-summary'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H39: Using caption elements to associate data table captions with data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H39'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H73: Using the summary attribute of the table element to give an overview of data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H73'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F46: Failure of Success Criterion 1.3.1 due to using th elements, caption elements, or non-empty summary attributes in layout tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F46'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist Checkpoint 1.3e: Tables', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webtableheaders.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Simple Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-simple.php'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Complex Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-complex.php'
              }  
            ]   
        },
        TABLE_2S: {
            ID:                    'Table Rule 2S (Single Data Table)',
            DEFINITION:            'If there is only one data table on a page, it %s have an effective caption with content',
            SUMMARY:               'Data tables %s have caption',
            TARGET_RESOURCES_DESC: '@th@ and @td[scope]@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @caption@ element or @summary@ attribute to provide an effective caption',
              NOT_APPLICABLE:  'More than one or no data tables on the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'Data table has an effective caption using the @caption@ with content: \'%1\'',
              PASS_2:   'Data table has an effective caption using the @summary@ attribute with content: \'%1\'',
              ACTION_1: 'Add @caption@ element or @summary@ attribute to provide an effective caption for the table, or if the table is really being used for layout or positioning add @role="presentation"@ to the @table@ element',
              HIDDEN_1: 'Caption for data table was not tested because the table is hidden from assistive technologies'
            },  
            PURPOSE: [
              'Effective caption of a table is important for people using speech, people with visual impairments and people with learning disabilities to help them understand the purpose of the table'                   
            ],
            TECHNIQUES: [
              'Use @caption@ element to provide an effective label',
              'Use @summary@ attribute to provide an effective label',
              'If the table is not used for tabular data, but instead for layout of content, use the @role="presentation"@ on all the table elements'
            ],
            MANUAL_CHECKS: [
              'Make sure the content of the effective label accurately and succinctly describes the purpose of the data table'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 11.2.2 Table Captions: The CAPTION element', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#h-11.2.2'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: summary attribute', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#adef-summary'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H39: Using caption elements to associate data table captions with data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H39'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H73: Using the summary attribute of the table element to give an overview of data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H73'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F46: Failure of Success Criterion 1.3.1 due to using th elements, caption elements, or non-empty summary attributes in layout tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F46'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist Checkpoint 1.3e: Tables', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webtableheaders.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Simple Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-simple.php'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Complex Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-complex.php'
              }  
            ]   
        },
        TABLE_2M: {
            ID:                    'Table Rule 2M (Multiple Data Tables)',
            DEFINITION:            'If there is more than one data table on a page, each data table %s have a effective caption with content',
            SUMMARY:               'Data tables %s have caption',
            TARGET_RESOURCES_DESC: '@caption@ and @table[summary]@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add @caption@ element or @summary@ attribute to provide an effective caption to the data table',
              ACTION_FAIL_P:   'Add @caption@ element or @summary@ attribute to each of the %N_F out of %N_T data tables to provide an effective caption',
              NOT_APPLICABLE:  'Only one or no data tables on the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    'Data table has an effective caption using the @caption@ with content: \'%1\'',
              PASS_2:    'Data table has an effective caption using the @summary@ attribute with content: \'%1\'',
              ACTION_1:  'Add @caption@ element or @summary@ attribute to provide an effective caption for the table, or if the table is really being used for layout or positioning add @role="presentation"@ to the @table@ element',
              HIDDEN_1:  'Effective caption was not tested because the table is hidden from assistive technologies'
            },  
            PURPOSE: [
              'Effective caption of a table is important for people using speech, people with visual impairments and people with learning disabilities to help them understand the purpose of the table'                   
            ],
            TECHNIQUES: [
              'Use @caption@ element to provide an effective label',
              'Use @summary@ attribute to provide an effective label',
              'If the table is not used for tabular data, but instead for layout of content, use the @role="presentation"@ on all the table elements'
            ],
            MANUAL_CHECKS: [
              'Make sure the content of the effective label accurately and succinctly describes the purpose of the data table'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 11.2.2 Table Captions: The CAPTION element', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#h-11.2.2'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: summary attribute', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#adef-summary'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H39: Using caption elements to associate data table captions with data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H39'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H73: Using the summary attribute of the table element to give an overview of data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H73'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F46: Failure of Success Criterion 1.3.1 due to using th elements, caption elements, or non-empty summary attributes in layout tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F46'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist Checkpoint 1.3e: Tables', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webtableheaders.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Simple Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-simple.php'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Complex Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-complex.php'
              }  
            ]   
        },
        TABLE_3: {
            ID:                    'Table Rule 3',
            DEFINITION:            'The effective caption content and effective summary content of each data table %s not be the same',
            SUMMARY:               '@caption@ %s be different from @summary@',
            TARGET_RESOURCES_DESC: '@caption@ element and @table[summary]@ attribute',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Update @caption@ element content to give the table a descriptive title and update the @summary@ attribute to provide a summary of the content or conclusions that can be reached by viewing the data in the table',
              ACTION_FAIL_P:  'Update @caption@ element content to give the table a descriptive title and update the @summary@ attribute to provide a summary of the content or conclusions that can be reached by viewing the data in each of the %N_F data tables on the page that have duplicate content for @caption@ element and @summary@ attribute',
              NOT_APPLICABLE: 'No data tables on the page with both a @caption@ element and @summary@ attribute'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'The text content of the @caption@ element is different that the text content of the @summary@ content',
              ACTION_1: 'Update the text content of the @caption@ element and the @summary@ attribute so they are different, use the @caption@ element to provide a title for the table and the @summary@ element to describe the data or a summary of the data',
              HIDDEN_1: 'Effective caption and effective summary not the same was not tested because the table is hidden from assistive technologies'
            },  
            PURPOSE: [
              '@caption@ element should be used to provide a title for a table so users of assistive technology can determine if they would like to explore the data',                   
              '@summary@ attribute should be used to provide a provide information about the data or a summary of the data in a table so users of assistive technology can get a summary of the table information'                   
            ],
            TECHNIQUES: [
              'Usethe  @caption@ element to provide a title for the table',
              'Use the @summary@ attribute to provide a summary of the content or conclusions that can be understood by viewing the data in the table',
              'If the table is not used for tabular data, but instead for layout of content, use the @role="presentation"@ on all the table elements'
            ],
            MANUAL_CHECKS: [
              'Verify the content of the @caption@ element text content accurately and succinctly describes the purpose of the data table',
              'Verify the content of the @summary@ attribute text content describes the data or a summary of the data in the table'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 11.2.2 Table Captions: The CAPTION element', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#h-11.2.2'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: summary attribute', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#adef-summary'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H39: Using caption elements to associate data table captions with data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H39'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H73: Using the summary attribute of the table element to give an overview of data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H73'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'F46: Failure of Success Criterion 1.3.1 due to using th elements, caption elements, or non-empty summary attributes in layout tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/F46'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist Checkpoint 1.3e: Tables', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webtableheaders.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Simple Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-simple.php'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Complex Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-complex.php'
              }  
            ]   
        },
        TABLE_4: {
            ID:                    'Table Rule 4',
            DEFINITION:            'Each data table header cell %s use th elements rather than td element with a scope attribute',
            SUMMARY:               'Header cells %s be @th@ elements',
            TARGET_RESOURCES_DESC: '@th@ and @td[scope]@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Change the @td[scope]@ element to a  @th@ element',
              ACTION_FAIL_P:  'Change the @td[scope]@ element to a  @th@ element for each of the %N_F header cells using @td[scope]@',
              NOT_APPLICABLE: 'No header cells identified on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    'The @th@ element is used for header cell',
              ACTION_1:  'Change the @td[scope]@ element to a  @th@ element',
              HIDDEN_1:  'Using @th@ element for header cells in a data table was not tested because the header cell is hidden from assistive technologies'
            },  
            PURPOSE: [
              '@th@ element is the web standards way to identify header cells in a table, makes the data table source code easier to read and debug for accessibility problems'                   
            ],
            TECHNIQUES: [
              'Use @th@ elements in the first row or column to identify row and column headers in a simple data tables',
              'Use @headers@ attribute on each @td@ element to identify header information in complex data tables',
              'Use @th@ element for cells used as header cells in the table'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML 4.01 Specification: 11.2.6 Table cells: The TH and TD elements', 
                url:   'http://www.w3.org/TR/html4/struct/tables.html#edef-TD'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H51: Using table markup to present tabular information', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H51'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H63: Using the scope attribute to associate header cells and data cells in data tables', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H63'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'IBM Web checklist Checkpoint 1.3e: Tables', 
                url:   'http://www-03.ibm.com/able/guidelines/web/webtableheaders.html'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Simple Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-simple.php'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'iCITA HTML Best Practices: Complex Data Table Example', 
                url:   'http://html.cita.illinois.edu/nav/dtable/dtable-example-complex.php'
              }  
            ]   
        },
        TITLE_1: {
            ID:                    'Title Rule 1',
            DEFINITION:            'Page %s have a @title@ element with content.',
            SUMMARY:               'Page %s have title',
            TARGET_RESOURCES_DESC: '@title@',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S: 'Add @title@ element to the @head@ element section with text content that describes the content or purpose of the page'
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'Page has @title@ element with content',
              ACTION_1: 'Add content to @title@ element',
              ACTION_2: 'Add @title@ element to page'
            },  
            PURPOSE: [
              'The TITLE element text content can be accessed by assistive technologies to understand the purpose of the web page.'
            ],
            TECHNIQUES: [
              'Use TITLE element text content to describe the content of a web page'
            ],
            MANUAL_CHECKS: [
              'The content of the @title@ element should describe the content or the purpose of the page'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML TITLE Element Specification', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-TITLE'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G88: Providing descriptive titles for Web pages', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G88'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H25: Providing a title using the title element', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H25'
              }
            ]
        },
        TITLE_2: {
            ID:            'Title Rule 2',
            DEFINITION:    '@title@ element text content %s describe the purpose or content of the page',
            SUMMARY:       '@title@ %s describe page',
            TARGET_RESOURCES_DESC: '@title@',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:   'Verify the @title@ element text content describes the purpose or the content of the page',
              ACTION_FAIL_S: 'Add @title@ element to the @head@ element section with text content that describes the content or purpose of the page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1:  'Verify the @title@ element text content describes the purpose or the content of the page',
              ACTION_1:        'Add content to @title@ element',
              ACTION_2:        'Add @title@ element to page'
            },  
            PURPOSE: [
              'The TITLE element text content can be accessed by assistive technologies to understand the purpose or content of the web page.'
            ],
            TECHNIQUES: [
              'Use TITLE element text content to describe the content of a web page',
              'Title should contain information about the website',
              'Title should contain information about the page in the website',
              'If the page is part of a sequence of web pages does the title indicate which step in the sequence'              
            ],
            MANUAL_CHECKS: [
              'Read the title of the page and determine if it describes which website it is a part',
              'Read the title of the page and determine if it describe which page you are in the website',
              'If the web page is part of a sequence of web pages does it describe which step it is in the sequence'              
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML TITLE Element Specification', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-TITLE'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G88: Providing descriptive titles for Web pages', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G88'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H25: Providing a title using the title element', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H25'
              }
            ]
        },
        TITLE_3: {
            ID:            'Title Rule 3',
            DEFINITION:    'The words in the @h1@ element text content %s match words in the @title@ element text content',
            SUMMARY:       '@h1@ %s match @title@ content',
            TARGET_RESOURCES_DESC: '@title@ and @h1@',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:   'Verify the @title@ element text content describes the purpose or the content of the page',
              ACTION_FAIL_S: 'Add @title@ element to the @head@ element section with text content that describes the content or purpose of the page'
            },
            NODE_RESULT_MESSAGES: {
              MANUAL_CHECK_1:      'Verify the @title@ element text content describes the purpose or the content of the page',
              ACTION_1: 'Add content to @title@ element',
              ACTION_2: 'Add @title@ element to page'
            },  
            PURPOSE: [
              'The TITLE element text content can be accessed by assistive technologies to understand the purpose or content of the web page.'
            ],
            TECHNIQUES: [
              'Use TITLE element text content to describe the content of a web page',
              'Title should contain information about the website',
              'Title should contain information about the page in the website',
              'If the page is part of a sequence of web pages does the title indicate which step in the sequence'              
            ],
            MANUAL_CHECKS: [
              'Read the title of the page and determine if it describes which website it is a part',
              'Read the title of the page and determine if it describe which page you are in the website',
              'If the web page is part of a sequence of web pages does it describe which step it is in the sequence'              
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HTML TITLE Element Specification', 
                url:   'http://www.w3.org/TR/html4/struct/global.html#edef-TITLE'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G88: Providing descriptive titles for Web pages', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G88'
              }, 
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'H25: Providing a title using the title element', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/H25'
              }
            ]
        },    
        VIDEO_1: {
            ID:                    'Video Rule 1',
            DEFINITION:            'Video only media (i.e. no audio content) %s have audio description or text description of the video content',
            SUMMARY:               'Video only %s have alternative',
            TARGET_RESOURCES_DESC: '@object@, @embed@ and @video@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Add audio description track or text description to video only media element',
              ACTION_FAIL_P:  'Add audio description track or text descriptions to each of the %N_F of the %N_T possible video only media elements',
              ACTION_MC_S:    'Verify if the video is video only media element.  If video only make sure the video has an audio description track or text description',
              ACTION_MC_P:    'Verify if the $N_MC of %N_T possible video only media elements.  For the media elements that are video only make sure those video elements have an audio description track or text description',
              NOT_APPLICABLE: 'No visible @object@, @embed@ or @video@ elements found on this page that could be used for video only (i.e. no audio content'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         '@%1@ video only element has a audio description track',
              PASS_2:         '@%1@ video only element has a text description',
              ACTION_1:       'Add audio description track or text description to @%1@ video only element',
              MANUAL_CHECK_1: 'Verify the @%1@ video element has audio description track or text description',
              MANUAL_CHECK_2: 'Verify the @%1@ element is being used for video only (i.e. no audio content), if it is video only verify that it has audio description track or text description ',
              HIDDEN_1:       'Video only media having an audio or text description was not tested because the @%1@ element is not visible on screen'
            },  
            PURPOSE: [
              'Audio description track and text descriptions provide a means for people cannot see the video to understand the content or information the video provides'                   
            ],
            TECHNIQUES: [
              'Various techniques to add based on the video formats and media players being supported, see your technology specific requirements for adding audio descriptions',
              'The HTML5 video element is designed to make it easier to support audio description tracks through the use of the @track@ element of @type=description@',
              'Use @aria-describedby@ attribute to point to a text description of the video only content'
            ],
            MANUAL_CHECKS: [
              'Audio description tracks can be heard when the video is being played, and they will be describing the content of the video',
              'Text descriptions maybe part of the web page or accessed through a link another page'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HMTL 5: The track element', 
                url:   'http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#the-track-element'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'W3C Accessible Rich Internet Applications (WAI-ARIA) 1.0: aria-describedby (property)', 
                url:   'http://www.w3.org/TR/wai-aria.html#aria-describedby'
              }                            
            ]
        },    
        VIDEO_2: {
            ID:                    'Video Rule 2',
            DEFINITION:            'Prerecorded video (with audio content) %s have synchronized caption',
            SUMMARY:               'Prerecorded video %s have caption',
            TARGET_RESOURCES_DESC: '@object@, @embed@ and @video@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:     'Verify the element is being used for prerecorded video (with audio content) and if it is prerecorded video make sure it has a caption track',
              ACTION_MC_P:     'Verify if any of the %N_MC out of %N_T possible prerecorded videos (with audio content), if they any are a prerecorded video make sure they have synchronized caption track',
              ACTION_FAIL_S:   'Add caption track to prerecorded video with audio content',
              ACTION_FAIL_P:   'Add caption track to each of the %N_F out of %N_T possible prerecorded videos with audio content',
              NOT_APPLICABLE:  'No visible @object@, @embed@ and @video@ elements found on the page that could be prerecorded video'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:                '%1 element has synchronized caption track',
              ACTION_1:   'Add synchronized caption track to @%1@ video element',
              MANUAL_CHECK_1:        'Verify the @%1@ video element has synchronized caption track',
              MANUAL_CHECK_2:        'Verify the @%1@ element is being used for video, if it is verify that it has a synchronized caption track',
              HIDDEN_1:                '@%1@ element is not visible on screen'
            },  
            PURPOSE: [
              'Captions provide a means for people who are deaf or hearing impaired to get the speech and sound content of a video'                   
            ],
            TECHNIQUES: [
              'Various techniques based on the video formats and media players you are supporting, please see your technology specific requirements for captions',
              'The HTML5 video element is designed to make it easier to support caption tracks through the use of the @track@ element',
              'Make sure the video player is capable of rendering captions by default or through user options'
            ],
            MANUAL_CHECKS: [
              'Captions should be visible when the video is playing',
              'The text of the captions should be synchronized with the speech'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HMTL 5: The track element', 
                url:   'http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#the-track-element'
              }                            
            ]
        },    
        VIDEO_3: {
            ID:                    'Video Rule 3',
            DEFINITION:            'Prerecorded video (with audio content) %s have audio or text description',
            SUMMARY:               'Prerecorded video %s have description',
            TARGET_RESOURCES_DESC: '@object@, @embed@ and @video@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify the element is prerecorded video (with audio content) and if it is prerecorded video make sure it has an audio description track or text description of the content of the video',
              ACTION_MC_P:    'Verify if any of the %N_MC out of %N_T possible media elements are prerecorded video (with audio content), if any are prerecorded video make sure they have either an audio description track or a text description of the video',
              ACTION_FAIL_S:  'Add audio description track or text description to prerecorded video with audio content',
              ACTION_FAIL_P:  'Add audio description tracks or text descriptions to each of the %N_F out of %N_T possible precorded video elements',
              NOT_APPLICABLE: 'No visible @object@, @embed@ and @video@ elements found on this page that could be prerecorded video'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:                '@%1@ element has a audio description track',
              PASS_2:                '@%1@ element has a text description',
              ACTION_1:   'Add audio description track or text description to @%1@ element',
              MANUAL_CHECK_1:        'Verify the @%1@ video element has audio description or text description',
              MANUAL_CHECK_2:        'Verify the @%1@ element is being used for prerecorded video, if it is verify that it has audio description track or text description ',
              HIDDEN_1:              'Prerecorded video having audo or text descriptions was not tested because the @%1@ element is not visible on screen'
            },  
            PURPOSE: [
              'Audio descriptions and text descriptions provide a means for people cannot see the video to understand the video content'                   
            ],
            TECHNIQUES: [
              'Various techniques to add based on the video formats and media players being supported, please see your technology specific requirements for captions',
              'The HTML5 video element is attempting to make it easier to support audio descriptions through the use of the @track@ element',
              'Use @aria-describedby@ attribute to point to a text description of the video content'
            ],
            MANUAL_CHECKS: [
              'Audio tracks can be heard when the video is being played, and they will be describing the content of the video',
              'Text descriptions maybe part of the web page or accessed by a link'            
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HMTL 5: The track element', 
                url:   'http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#the-track-element'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'W3C Accessible Rich Internet Applications (WAI-ARIA) 1.0: aria-describedby (property)', 
                url:   'http://www.w3.org/TR/wai-aria.html#aria-describedby'
              }                            
            ]
        },
        VIDEO_4: {
            ID:                    'Video Rule 4',
            DEFINITION:            'Live video %s have synchronized caption',
            SUMMARY:               'Live video %s have caption',
            TARGET_RESOURCES_DESC: '@object@, @embed@ and @video@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify the element is being used for live video (with audio content) and if it is a live video make sure it has a synchronized caption track',
              ACTION_MC_P:    'Verify if any of the %N_MC of the %N_T possible live videos, if they any are a live video (with audio content) make sure they have synchronized caption tracks',
              ACTION_FAIL_S:  'Add synchronized caption track to live video',
              ACTION_FAIL_P:  'Add synchronized caption tracks to each of the %N_F out of %N_T possible live videos',
              NOT_APPLICABLE: 'No visible @object@, @embed@ and @video@ elements found on this page that could be live video'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:          '%1 element has synchronized caption track',
              ACTION_1:        'Add synchronized caption to @%1@ live video element',
              MANUAL_CHECK_1:  'Verify the @%1@ live video element has a synchronized caption track',
              MANUAL_CHECK_2:  'Verify the @%1@ element is being used for live video, if it is verify that it has synchronized caption track',
              HIDDEN_1:        'Live video having synchronized captions was not tested because the @%1@ element is not visible on screen'
            },  
            PURPOSE: [
              'Captions provide a means for people who are deaf or hearing impaired to get the speech and sound content of a video'                   
            ],
            TECHNIQUES: [
              'Various techniques based on the video formats and media players you are supporting, please see your technology specific requirements for captions',
              'The HTML5 video element is designed to make it easier to support caption tracks through the use of the @track@ element',
              'Make sure the video player is capable of rendering captions by default or through user options'
            ],
            MANUAL_CHECKS: [
              'Captions should be visible when the live video is playing and the text of the captions should be synchronized with the speech'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HMTL 5: The track element', 
                url:   'http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#the-track-element'
              }                            
            ]
        },      
        VIDEO_5: {
            ID:                    'Video Rule 5',
            DEFINITION:            'Video (with audio content) %s have audio description track',
            SUMMARY:               'Video %s have audio description track',
            TARGET_RESOURCES_DESC: '@object@, @embed@ and @video@ elements',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify the element is prerecorded video (with audio content) and if the element is prerecorded video make sure it has an audio description track',
              ACTION_MC_P:    'Verify if any of the %N_MC out of %N_T possible prerecorded video (with audio content) elements, if any elements are a prerecorded video make sure each has an audio description track',
              ACTION_FAIL_S:  'Add audio description track to prerecorded video element',
              ACTION_FAIL_P:  'Add audio description tracks to each of the %N_F the prerecorded video elements',
              NOT_APPLICABLE: 'No visible @object@, @embed@ and @video@ elements found on this page that could be prerecorded video'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         '@%1@ element has a audio description track',
              ACTION_1:       'Add audio description track to @%1@ prerecorded video element',
              MANUAL_CHECK_1: 'Verify the @%1@ video element has audio description track',
              MANUAL_CHECK_2: 'Verify the @%1@ element is being used for prerecorded video, if it is verify that it has audio description track',
              HIDDEN_1:       'Video (with audio content) having audio description track was not tested because the @%1@ element is not visible on screen'
            },  
            PURPOSE: [
              'Audio descriptions and text descriptions provide a means for people cannot see the video to understand the video content'                   
            ],
            TECHNIQUES: [
              'Various techniques to add based on the video formats and media players you are supporting, please see your technology specific requirements for audio descriptions',
              'The HTML5 video element is attempting to make it easier to support audio descriptions through the use of the text track element',
              'Use aria-describedby attribute to point to a text description of the video only content'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'HMTL 5: The track element', 
                url:   'http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#the-track-element'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'ARIA: aria-describedby', 
                url:   ''
              }                            
            ]
        },    
        WIDGET_1: {
            ID:                    'Widget Rule 1',
            DEFINITION:            'Widgets %s have an accessible name',
            SUMMARY:               'Widget %s have name',
            TARGET_RESOURCES_DESC: 'Elements with @role@ attribute values that are defined as widgets',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:     'Widget may need an accessible name',
              ACTION_MC_P:     '%N_MC out of %N_T widgets may need an accessible name',
              ACTION_FAIL_S:   'Add accessible name to the widget',
              ACTION_FAIL_P:   'Add an accessible name to %N_F out of %N_T widgets',
              NOT_APPLICABLE:  'No widgets on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:          '%1 widget has accessible name',
              MANUAL_CHECK_1:  '%1 widget may require an accessible name depending on context (i.e multiple widgets with the same role) in the page, adding an accessible name will improve accessibility',
              ACTION_1:        'Add accessible name to %1 widget',
              HIDDEN_1:        'Accessible name for widget was not tested because %1 widget is hidden from assistive technologies and/or not visible on screen'
            },
            PURPOSE: [
              'A name associated with a widget insures that information about the widget is spoken by screen readers when it receives focus'                   
            ],
            TECHNIQUES: [
              'In some cases the child text nodes and @alt@ from descendant image elements will be used as the name for a widget',
              'Use @aria-labelledby@ attribute to reference the id(s) of the elements on the page that describe the purpose of the widget',
              'Use @aria-label@ attribute to provide a explicit text description of the purpose of the widget',
              'Elements that have container widget roles typically do not receive keyboard focus, but giving them an accessible name provides assistive technologies a more accurate description of the purpose of the widget'
            ],
            MANUAL_CHECKS: [
              'When there is more than one widget of the same type on a page, they need an accessible name for users to uniquely identify the form control'
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Accessible Name Calculation', 
                url:   'http://www.w3.org/TR/wai-aria/roles#namecalculation'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: aria-labelledby', 
                url:   'http://www.w3.org/TR/wai-aria/states_and_properties#aria-labelledby'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: aria-label', 
                url:   'http://www.w3.org/TR/wai-aria/states_and_properties#aria-label'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },                             
              { type:  OpenAjax.a11y.REFERENCES.TECHNIQUE, 
                title: 'WAI-ARIA 1.0 Authoring Practices', 
                url:   'http://www.w3.org/TR/wai-aria-practices/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
        },
        WIDGET_2: {
            ID:                    'Widget Rule 2',
            DEFINITION:            'Elements with @onClick@ events %s be a link, button or have a widget role with tabindex',
            SUMMARY:               '@onClick@ %s have role',
            TARGET_RESOURCES_DESC: 'Elements with @onClick@ attribute values that are defined as widgets',
            RULE_RESULT_MESSAGES: {
              ACTION_MC_S:    'Verify that any child elements that can respond to element with an @onclick@ event are a link, form control or has a widget role, and can be accessed with the keyboard alone',
              ACTION_MC_P:    'Verify that any child elements that can respond to $N_MC elements with an @onclick@ event are a link, form control or has a widget role, and can be accessed with the keyboard alone',
              ACTION_FAIL_S:  'Add widget role name to element',
              ACTION_FAIL_P:  'Add widget roles to each of the %N_F elements',
              NOT_APPLICABLE: 'No elements with @onClick@ events on the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:         '@%1@ element has a widget role',
              PASS_2:         '@%1@ element is a form control',
              PASS_3:         '@%1@ element is a link',
              ACTION_1:       'Add a @tabindex@ attribute with a numeric value to make with @%1@ element with @%2@ widget role keyboard accessible',
              ACTION_2:       'Add widget role to the @%1@ element',
              ACTION_3:       'Add widget role to the @%1@ element and a @tabindex@ attribute with a numeric value to make it keyboard accessible',
              MANUAL_CHECK_1: 'The @%1@ element has an @onclick@ event, verify any child elements that can respond to the @onclick@ event are a link, form control or have a widget role, and can be access with the keyboard alone',
              HIDDEN_1:       'Elements with onClick events having a @role@ was not tested because %1 element with @onClick@ event is hidden from assistive technologies and/or not visible on screen'
            },
            PURPOSE: [
              'Elements with @onClick@ event handlers must be a link, form control or have a widget role'                   
            ],
            TECHNIQUES: [
              'Use ARIA widget role on non-form controls to describe their function on the page',
              'Use @tabindex@ attribute of "0" if the role of the element is a button or link',
              'Use @tabindex@ attribute of "-1" if the role of the element is NOT a button or link, provide keyboard event handlers to control selection of elements in the widget'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'WAI-ARIA 1.0 Authoring Practices: Tabindex for managing focus', 
                url:   'http://www.w3.org/TR/2010/WD-wai-aria-practices-20100916/#kbd_focus'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
        },
        WIDGET_3: {
            ID:                    'Widget Rule 3',
            DEFINITION:            '@role@ attribute value %s be a widget, section, landmark or live region role',
            SUMMARY:               '@role@ %s be valid',
            TARGET_RESOURCES_DESC: 'Elements with @role@ attribute values',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add a valid widget, section, landmark or live region role value to the element',
              ACTION_FAIL_P:   'Add a valid widget, section, landmark or live region role values to %N_F out of %N_T elements with @role@ attributes',
              NOT_APPLICABLE:  'No elements with @role@ attribute on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:     '@%1@ is a widget role',
              PASS_2:     '@%1@ is a landmark role',
              PASS_3:     '@%1@ is a live region role',
              PASS_4:     '@%1@ is a section role',
              ACTION_1:   '@%1@ is an abstract ARIA role, change the role attribute to a widget, landmark or live region role',
              ACTION_2:   'The @role@ attribute is an empty string, change the @role@ attribute value to an appropriate widget, landmark, section or live region role',
              ACTION_3:   '@%1@ is not a defined ARIA role, change the @role@ attribute value to an appropriate widget, landmark, section or live region role',
              HIDDEN_1:   '@role@ attribute value was not validated because the %1 element is hidden from assistive technologies and/or not visible on screen'
            },
            PURPOSE: [
              'Elements with @role@ attributes describe the section of a document (i.e landmarks) and the types of interactive elements (i.e. widgets) to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use ARIA landmark roles to describe the sections of a web page',
              'Use ARIA widget roles to describe interactive elements on a web page'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Landmark Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#landmark_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
        },
        WIDGET_4: {
            ID:                    'Widget Rule 4',
            DEFINITION:            'ARIA property and state values %s be valid types',
            SUMMARY:               'ARIA values %s be valid',
            TARGET_RESOURCES_DESC: 'Elements with aria attributes',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Change ARIA attribute to a valid type',
              ACTION_FAIL_P:  'Change %N_F out of %N_T ARIA attributes to a valid types',
              NOT_APPLICABLE: 'No ARIA attributes on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   'The @%1@ attribute with the value "@%2@" is a valid token',
              PASS_2:   'The @%1@ attribute with the value "@%2@" is a valid "%3" type',
              ACTION_1: 'The @%1@ attribute with the value "@%2@" must change to one of the following values: %3',
              ACTION_2: 'The @%1@ attribute with the value "@%2@" must change to one or more of the following values: %3',
              ACTION_3: 'The @%1@ attribute with the value "@%2@" must change to a value with type of "%3"',
              HIDDEN_1: 'ARIA attribute value was not tested for validity because the @%1@ attribute with the value "@%2@" is hidden from assistive technologies and not visible on screen'
            },
            PURPOSE: [
              'ARIA attributes must be a valid type to accurately describe web content to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use valid values for ARIA attributes',
              'Check W3C WAI Accessible Rich Internet Applications specifications for allowed values for ARIA attributes'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Supported Property and States', 
                url:   'http://www.w3.org/TR/wai-aria/states_and_properties'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
      },
      WIDGET_5: {
            ID:                    'Widget Rule 5',
            DEFINITION:            'ARIA property or state %s be defined',
            SUMMARY:               'ARIA attribute %s be defined',
            TARGET_RESOURCES_DESC: 'Elements with aria attributes',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Change ARIA attribute to a defined property or state',
              ACTION_FAIL_P:  'Change all %N_F out of %N_T ARIA attributes to a defined properties or states',
              NOT_APPLICABLE: 'No undefined ARIA attributes on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    'The @%1@ attribute is a defined ARIA property or state',
              ACTION_1:  'The @%1@ attribute must be changed to a defined ARIA property or state',
              HIDDEN_1:  'Valid ARIA attribute was not tested becasue the @%1@ attribute with the value "@%2@" is hidden from assistive technologies and/or not visible on screen'
            },
            PURPOSE: [
              'ARIA attributes must be defined properties or states to accurately describe web content to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use defined ARIA properties and states in the ARIA specification',
              'Check W3C WAI Accessible Rich Internet Applications specifications for allowed values for ARIA attributes'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Supported Property and States', 
                url:   'http://www.w3.org/TR/wai-aria/states_and_properties'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
      },
      WIDGET_6: {
            ID:                    'Widget Rule 6',
            DEFINITION:            'Widgets %s define required properties and states',
            SUMMARY:               'Widgets %s have properties',
            TARGET_RESOURCES_DESC: 'Widgets with required properties and states',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add required properties and states to widget',
              ACTION_FAIL_P:   'Add required properties and states to the %N_F of the %N_T widgets with required properties and/or states on the page',
              NOT_APPLICABLE:  'No widgets with required properties and states on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@%1@ widget has the following required ARIA properties and states: %2',
              ACTION_1: 'Add one or more of the required ARIA properties and states (i.e. "%2") to the @%1@ widget',
              HIDDEN_1: 'Required ARA properties and states was not tested because the %1 widget is hidden from assistive technologies and/or not visible on screen'
            },
            PURPOSE: [
              'ARIA roles, properties and states describes the features of interactive widgets to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use required ARIA properties to describe the features and options of a widget'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
        },
      WIDGET_7: {
            ID:                    'Widget Rule 7',
            DEFINITION:            'Widgets %s have required child roles',
            SUMMARY:               'Widgets %s have child roles',
            TARGET_RESOURCES_DESC: 'Widgets with required owned elements',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add required child roles to child elements in the widget',
              ACTION_FAIL_P:   'Add required child roles to child elements in the %N_F out of %N_T widgets with required child elements',
              NOT_APPLICABLE:  'No widgets with required child ARIA elements on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    '@%1@ widget has at least one required owned elements: %2',
              ACTION_1:  '@%1@ widget is MISSING one or more of following required owned elements: %2',
              HIDDEN_1:  'Required child widgets was not tested because the %1 widget is hidden from assistive technologies and not visible on screen'
            },
            PURPOSE: [
              'ARIA roles, properties and states describes the features of interactive widgets to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use required ARIA owned elements to describe the features and options of a widget'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
        },
     WIDGET_8: {
            ID:                    'Widget Rule 8',
            DEFINITION:            'Widgets %s have required parent role',
            SUMMARY:               'Widgets %s have parent',
            TARGET_RESOURCES_DESC: 'Widgets with required parent role',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add required parent role to the widget',
              ACTION_FAIL_P:   'Add required parent role to the %N_F of the %N_T widgets that require a parent role',
              NOT_APPLICABLE:  'No widgets with required parent role on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@%1@ widget is a child of the a @%2@ role',
              ACTION_1: 'Create a parent widget with the role of @%1@ for this @%2@ widget',
              ACTION_2: 'Create a parent widget with the one of the required roles (i.e. @%1@) for this @%2@ widget',
              HIDDEN_1: 'Required parent widgets was not tested because the %1 widget is hidden from assistive technologies and/or not visible on screen'
            },
            PURPOSE: [
              'ARIA roles, properties and states describes the features of interactive widgets to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use required parent roles to describe the features and options of a widget'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'Accessible jQuery-ui Components Demonstration', 
                url:   'http://access.aol.com/aegis/#goto_slider'
              }                            
            ]
        },
     WIDGET_9: {
            ID:                    'Widget Rule 9',
            DEFINITION:            'Widgets %s be owned by only one parent widget',
            SUMMARY:               'Only one owner',
            TARGET_RESOURCES_DESC: 'Widgets with required parent roles',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Update widgets with aria-owns to make sure it only references a child widget once',
              ACTION_FAIL_P:   'Update %N_F out of %N_T widgets with aria-owns to make sure they reference a child widget only once',
              NOT_APPLICABLE:  'No widgets using aria-owns on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@%1@ child widget is referenced only by @%2@ parent widget with aria-owns',
              ACTION_1: 'Update references of @%1@ parent widgets with aria-owns to reference @%2@ child widget only once',
              HIDDEN_1: 'Widgets owned by more than one parent widget was not tested becasue the %1 parent widget with aria-owns is hidden from assistive technologies and not visible on screen'
            },
            PURPOSE: [
              'ARIA roles, properties and states describes the features of interactive widgets to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Parent widget roles with aria-owns must accurately describe the parent relationships, a child widget can only have one parent widget'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              }                            
            ]
        },
     WIDGET_10: {
            ID:                    'Widget Rule 10',
            DEFINITION:            'Range widget %s have value between minimum and maximum values',
            SUMMARY:               'Value in range',
            TARGET_RESOURCES_DESC: 'Range widgets ',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:  'Update range attributes of the range widget so the @aria-valuenow@ attribute is in the range defined by @aria-valuemin@ and @aria-valuemax@ attributes',
              ACTION_FAIL_P:  'Update range attributes of the %N_F out of %N_T range widgets so the @aria-valuenow@ attribute of each widget is in the range defined by @aria-valuemin@ and @aria-valuemax@ attributes',
              NOT_APPLICABLE: 'No range widgets on the page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:    '@%1@ widget is using @aria-valuetext@ attribute which overrides the @aria-valuenow@ attribute for describing the value of the range',
              PASS_2:    '@%1@ widget value of %2 is in the range %3 and %4',
              PASS_3:    '@%1@ widget has the range %3 and %4, and by not including the @aria-valuenow@ attribute the value of the progress-bar is considered indeterminate',
              ACTION_1:  'Update the numeric values of @aria-valuenow@ (%1), @aria-valuemin@ (%2) and @aria-valuemax@ (%3) so the @aria-valuenow@ value is in range',
              ACTION_2:  'Update the numeric values of @aria-valuemin@ (%1) and @aria-valuemax@ (%2) so the @aria-valuemin@ value is less than the @aria-valuemax@ value',
              ACTION_3:  'For progress bar update the numeric values or add @aria-valuemin@ (%2) and @aria-valuemax@ (%3) attributes and when state of progress is known use the @aria-valuenow@ attribute value to communicate the current state of progress',
              ACTION_4:  'Update or create @%1@ attribute to be a numeric value',
              ACTION_5:  'Update or create @%1@ attributes to be a numeric values',
              HIDDEN_1:  'Widget range values were not tested becasue the %1 range widget is hidden from assistive technologies'
            },
            PURPOSE: [
              'ARIA roles, properties and states describes the features of interactive widgets to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use the @aria-valuenow@, @aria-valuemin@ and @aria-valuemax@ are accurately defined'
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              }                            
            ]
        },
     WIDGET_11: {
            ID:                    'Widget Rule 11',
            DEFINITION:            'Elements with keyboard, mouse and/or drag events %s have widget roles or interactive elements (i.e. form controls or links)',
            SUMMARY:               'Keyboard/Mouse/drag events %s have roles',
            TARGET_RESOURCES_DESC: 'Elements with mouse events',
            RULE_RESULT_MESSAGES: {
              ACTION_FAIL_S:   'Add ARIA widget role to the element with events or child elements that accurately describe the user options and actions available to the user',
              ACTION_FAIL_P:   'Add ARIA widget roles to the %N_F out of %N_T elements with events or their child elements that accurately describe the user options and actions available',
              NOT_APPLICABLE:  'No elements with keyboard, mouse or drag events found on this page'              
            },
            NODE_RESULT_MESSAGES: {
              PASS_1:   '@%1@ widget has the following keyboard, mouse or drag event(s): %2',
              PASS_2:   '@%1@ element has the following keyboard, mouse or drag event(s): %2',
              PASS_3:   '@%1@ element with the following keyboard, mouse or drag event(s): %2 ; has descendant elements with widget roles or interactive elements',
              ACTION_1: 'Add widget role(s) to the element and/or its descendants that accurately describe the user options and actions of the @%1@ element with the following keyboard, mouse or drag events: %2',
              HIDDEN_1: 'Roles for interactive elements was not tested because the %1 element is hidden from assistive technologies with following keyboard, mouse or drag events: %2'
            },
            PURPOSE: [
              'ARIA roles, properties and states describes the features of interactive widgets to users of assistive technologies, especially screen reader users'                   
            ],
            TECHNIQUES: [
              'Use the @role@ attribute to describe the type of widget associated with the mouse events',
              'Use ARIA properties and states attributes to describe features of each widget '
            ],
            MANUAL_CHECKS: [
            ],
            INFORMATIONAL_LINKS: [
              { type:  OpenAjax.a11y.REFERENCES.SPECIFICATION, 
                title: 'Accessible Rich Internet Applications (WAI-ARIA) 1.0 Specification: Widget Roles', 
                url:   'http://www.w3.org/TR/wai-aria/roles#widget_roles'
              },
              { type:  OpenAjax.a11y.REFERENCES.WCAG_TECHNIQUE, 
                title: 'G108: Using markup features to expose the name and role, allow user-settable properties to be directly set, and provide notification of changes', 
                url:   'http://www.w3.org/TR/2012/NOTE-WCAG20-TECHS-20120103/G108'
              },
              { type:  OpenAjax.a11y.REFERENCES.EXAMPLE, 
                title: 'OAA Web Accessibility ARIA Examples', 
                url:   'http://oaa-accessibility.org/examples/'
              }                            
            ]
        }
   }     
});
/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*            OpenAjax Alliance Media Rules                         */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([

/**
 * @object AUDIO_1
 *
 * @desc Pre-recorded audio must have captions or text transcripts
 */ 
 
{ rule_id             : 'AUDIO_1', 
  last_updated        : '2012-10-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
  wcag_primary_id     : '1.2.1',
  wcag_related_ids    : ['1.2.2', '1.2.4', '1.2.9'],
  target_resources    : ['embed', 'object', 'audio'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tag_name', 'name', 'type', 'src', 'alt'],
  language_dependency : "",
  validate          : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var MEDIA       = OpenAjax.a11y.MEDIA;
  
    var media_elements     = dom_cache.media_cache.media_elements;
    var media_elements_len = media_elements.length;

    for (var i = 0; i < media_elements_len; i++ ) {
      var me = media_elements[i];
      var tag_name = me.dom_element.tag_name;
      
      if (me.is_audio === MEDIA.YES && me.is_video === MEDIA.NO ) {
        
        if (me.has_caption === MEDIA.YES) {
          rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_1', [tag_name]);
        }
        else if (me.has_text_alternative === MEDIA.YES) {
          rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_2', [tag_name]);
        }
        else if (me.has_caption === MEDIA.MAYBE || me.has_text_alternative === MEDIA.MAYBE) {
          rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_1', [tag_name]);
        }
        else {
          rule_result.addResult(TEST_RESULT.FAIL, me, 'ACTION_1', [tag_name]);
        }    
        
      }
      else {
        if (me.is_audio === MEDIA.MAYBE) {
          rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_2', [tag_name]);
        }
      }
    }
  } // end validate function
}

]);
//
// OpenAjax Alliance Rules 
// Rule group: Styling Rules
//
OpenAjax.a11y.all_rules.addRulesFromJSON([
      
 // ------------------------
 // Color 1: Color contrast ratio must be > 4.5 for normal text, or > 3.1 for large text
 // Group 7: Styling Rule
 // 
 // Last update: 2011-03-31
 // ------------------------
 
{ rule_id             : 'COLOR_1', 
  last_updated        : '2012-06-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER,
  wcag_primary_id     : '1.4.3',
  wcag_related_ids    : ['1.4.1','1.4.6'],
  target_resources    : ['textnodes'],
  cache_dependency    : 'text_cache',
  mc_properties       : ['background_image', 'color_hex'],
  resource_properties : ['color_hex', 'background_color_hex', 'background_image', 'is_large_font', 'color_contrast_ratio'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
      var MIN_CCR_NORMAL_FONT = 4.5;
      var MIN_CCR_LARGE_FONT  = 3.1;
  
      var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
   
      var cc_items     = dom_cache.text_cache.text_nodes;
      var cc_items_len = cc_items.length;
     
      for (var i = 0; i < cc_items_len; i++) {

        var test_result = TEST_RESULT.PASS;
        var message_id = '';
        var args = [];

        var cc_item = cc_items[i];
        var pe = cc_item.parent_element;
        var cs = pe.computed_style;

        // if color contrast raio is undefined, skip this item
        if (!cs.color_contrast_ratio) continue;

        if (cs.is_visible_onscreen === VISIBILITY.VISIBLE) {

          if ((cs.color_contrast_ratio >= MIN_CCR_NORMAL_FONT) ||
            ((cs.color_contrast_ratio >= MIN_CCR_LARGE_FONT) && (cs.is_large_font))) {
     
            // Passes color contrast requirements
            if (cs.background_image != "none") {
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, cc_item, 'MANUAL_CHECK_1', [cs.color_contrast_ratio]);
            }           
            else {
              rule_result.addResult(TEST_RESULT.PASS, cc_item, 'PASS_1', [cs.color_contrast_ratio]);
            }
          }
          else {
          
            // Fails color contrast requirements
            if (cs.background_image === "none") {
              rule_result.addResult(TEST_RESULT.FAIL, cc_item, 'ACTION_1', [cs.color_contrast_ratio]);
            }
            else {
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, cc_item, 'MANUAL_CHECK_2', [cs.color_contrast_ratio]);
            }     
          }
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, cc_item, 'HIDDEN_1', []);        
        }
        
      } // end loop  
      
    } // end validate function
 }
 ]); 


    

/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Control Rules                                 */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([

/**
 * @object FOCUS_1
 * 
 * @desc Focus order
 */
     
{ rule_id             : 'FOCUS_1', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT,
  last_updated        : '2013-07-03', 
  wcag_primary_id     : '2.4.3',
  wcag_related_ids    : ['2.1.1', '2.1.2', '2.4.7', '3.2.1'],
  target_resources    : ['a', 'applet', 'area', 'button', 'input', 'object', 'select', 'area', 'widgets'],
  cache_dependency    : 'keyboard_focus_cache',
  resource_properties : [],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var page_element = dom_cache.keyboard_focus_cache.page_element;  

//     OpenAjax.a11y.logger.debug(" Page Element: " + page_element + "  " + page_element.dom_element);

     var interactive_elements     = dom_cache.keyboard_focus_cache.interactive_elements;
     var interactive_elements_len = interactive_elements.length;

     var tab_count = 0;
     var visible_count = 0;

     for (var i = 0; i < interactive_elements_len; i++) {
     
       var ie = interactive_elements[i];
       
       var de = ie.dom_element;
       if (!de) de =ie;
       
       var cs = de.computed_style;
       
       if ((cs.is_visible_to_at    === VISIBILITY.VISIBLE) ||
           (cs.is_visible_onscreen === VISIBILITY.VISIBLE)) {
           
         visible_count++;
         
         if (de.tab_index >= 0) { 
           if (de.is_widget) {
             // only include widgets that can be part of the tab order
             if (de.is_tab_stoppable) {
                tab_count++;
               rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_1', [de.tag_name, de.role]);
             }
           }
           else {
             tab_count++;
             rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_2', [de.tag_name]);
           }
         }
         else {
           if (de.is_widget) {
             // only include widgets that can be part of the tab order
             if (de.is_tab_stoppable) {
               rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_3', [de.tag_name, de.role, de.tab_index]);
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_4', [de.tag_name, de.tab_index]);
           }         
         }
         
       }     
       else {
       
         if (de.is_widget) {
           // only include widgets that can be part of the tab order
           if (de.is_tab_stoppable) {
             rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', [de.tag_name, de.role]);      
           }
         }
         else {
           rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_2', [de.tag_name]);                
         }
       }  
     }  // endfor
 
 //    OpenAjax.a11y.logger.debug(" Visible count: " + visible_count + "  Tab count: " + tab_count);

     if (visible_count > 1) { 
 
       if (tab_count === visible_count) {
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', [tab_count]);
       }
       else {
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_2', [tab_count, (visible_count-tab_count)]);             
       }
     
     }


   } // end validation function   
},

/**
 * @object FOCUS_2
 * 
 * @desc Focus style
 */
     
{ rule_id             : 'FOCUS_2', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER,
  last_updated        : '2013-07-03', 
  wcag_primary_id     : '2.4.7',
  wcag_related_ids    : ['2.1.1', '2.1.2',  '2.4.3', '3.2.1'],
  target_resources    : ['a', 'applet', 'area', 'button', 'input', 'object', 'select', 'area', 'widgets'],
  cache_dependency    : 'keyboard_focus_cache',
  resource_properties : ['tabindex'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var page_element = dom_cache.keyboard_focus_cache.page_element;  

//     OpenAjax.a11y.logger.debug(" Page Element: " + page_element + "  " + page_element.dom_element);

     var interactive_elements     = dom_cache.keyboard_focus_cache.interactive_elements;
     var interactive_elements_len = interactive_elements.length;

     var visible_interactive_count = 0;

     for (var i = 0; i < interactive_elements_len; i++) {
     
       var ie = interactive_elements[i];
       
       var de = ie.dom_element;
       if (!de) de =ie;
       
       var cs = de.computed_style;
       
       if (cs.is_visible_onscreen === VISIBILITY.VISIBLE) {
       
         visible_interactive_count++;
           
         if (de.is_widget) {             
           rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_1', [de.tag_name, de.role]);
         }
         else {
           rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_2', [de.tag_name]);
         }
         
       }
       else {

         if (de.is_widget) {             
           rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', [de.tag_name, de.role]);
         }
         else {
           rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_2', [de.tag_name]);
         }

       }  
     }  // endfor
 
//     OpenAjax.a11y.logger.debug(" Visible Interactive Count: " + visible_interactive_count);

     if (visible_interactive_count > 1) { 
 
       if (visible_interactive_count === interactive_elements_len) {
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', [interactive_elements_len]);
       }
       else {
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_2', [visible_interactive_count, (interactive_elements_len - visible_interactive_count)]);             
       }
     
     }


   } // end validation function   

},

/**
 * @object FOCUS_3
 * 
 * @desc Target of a link does not go to a page with popup windows
 */
     
{ rule_id             : 'FOCUS_3', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.LINKS,
  last_updated        : '2013-07-03', 
  wcag_primary_id     : '3.2.1',
  wcag_related_ids    : ['2.1.1', '2.1.2',  '2.4.3', '2.4.7'],
  target_resources    : ['a', 'area', 'select'],
  cache_dependency    : 'keyboard_focus_cache',
  resource_properties : ['tabindex'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
//     OpenAjax.a11y.logger.debug(" Page Element: " + page_element + "  " + page_element.dom_element);

     var link_elements     = dom_cache.links_cache.link_elements;
     var link_elements_len = link_elements.length;

     var visible_interactive_count = 0;

     for (var i = 0; i < link_elements_len; i++) {
     
       var le = link_elements[i];
       
       var de = le.dom_element;
       if (!de) de =le;
       
       var cs = de.computed_style;
       
       if (cs.is_visible_to_at === VISIBILITY.VISIBLE) {
       
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, le, 'MANUAL_CHECK_1', [de.tag_name]);
         
       }
       else {
         rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [de.tag_name, de.role]);
       }  
     }  // endfor
 
   } // end validation function   
},

/**
 * @object FOCUS_4
 * 
 * @desc Select elements with onchange events
 */
     
{ rule_id             : 'FOCUS_4', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  last_updated        : '2013-07-03', 
  wcag_primary_id     : '3.2.1',
  wcag_related_ids    : ['2.1.1', '2.1.2',  '2.4.3', '2.4.7'],
  target_resources    : ['a', 'area', 'select'],
  cache_dependency    : 'keyboard_focus_cache',
  resource_properties : ['tabindex'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
//     OpenAjax.a11y.logger.debug(" Page Element: " + page_element + "  " + page_element.dom_element);

     var control_elements     = dom_cache.controls_cache.control_elements;
     var control_elements_len = control_elements.length;

     for (var i = 0; i < control_elements_len; i++) {
     
       var ce = control_elements[i];
       
       var de = ce.dom_element;
       if (!de) de =ce;
       
       var cs = de.computed_style;
       
       if ((de.tag_name === 'select') &&
            de.events.has_change) {
       
         if (cs.is_visible_to_at === VISIBILITY.VISIBLE) {   
           rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ce, 'MANUAL_CHECK_1', [de.tag_name]);
         }
         else {
           rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [de.tag_name, de.role]);
         }  
       }  
     }  // endfor
 
   } // end validation function   
}

]); 


    

/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Control Rules                                 */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([

/**
 * @object CONTROL_1
 * 
 * @desc textarea, select and input elements of type text, 
 *       password, checkbox, radio and file must have an 
 *       accessible label
 * 
 */
     
{  rule_id             : 'CONTROL_1',
   last_updated        : '2011-09-16', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
   wcag_primary_id     : '3.3.2',
   wcag_related_ids    : ['1.3.1', '2.4.6'],
   target_resources    : ['input[type="checkbox"]', 'input[type="radio"]', 'input[type="text"]', 'input[type="password"]', 'input[type="file"]', 'select', 'textarea'],
   cache_dependency    : 'controls_cache',
   mc_properties       : [],
   resource_properties : ['computed_label', 'fieldset_element', 'computed_label_source', 'name_attribute'],
   language_dependency : "",
   validate            : function (dom_cache, rule_result) {
   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
   
     var tag_name;
     var type;
   
     var control_elements   = dom_cache.controls_cache.control_elements;
     var control_elements_len = control_elements.length;
       
     // Check to see if valid cache reference
     if (control_elements && control_elements_len) {
     
       for (var i = 0; i < control_elements_len; i++) {
         var ce = control_elements[i];
  
         var control_type = ce.control_type;

         if (control_type === OpenAjax.a11y.CONTROL_TYPE.CHECKBOX ||
             control_type === OpenAjax.a11y.CONTROL_TYPE.FILE     ||
             control_type === OpenAjax.a11y.CONTROL_TYPE.PASSWORD ||
             control_type === OpenAjax.a11y.CONTROL_TYPE.RADIO    ||
             control_type === OpenAjax.a11y.CONTROL_TYPE.SELECT   ||
             control_type === OpenAjax.a11y.CONTROL_TYPE.TEXT     ||
             control_type === OpenAjax.a11y.CONTROL_TYPE.TEXTAREA ) {
             
           if (ce.dom_element.computed_style.is_visible_to_at == OpenAjax.a11y.VISIBILITY.VISIBLE) {
     
             if (ce.computed_label && ce.computed_label.length) {
               rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', [ce.type.toUpperCase()]);     
             }
             else {
               rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', [ce.type.toUpperCase()]);     
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [ce.type.toUpperCase()]);     
           }
         }  
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object CONTROL_2
 * 
 * @desc Every input type image must have an alt or title attribute with content
 */
     
{  rule_id             : 'CONTROL_2', 
   last_updated        : '2011-09-16', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
   wcag_primary_id     : '3.3.2',
   wcag_related_ids    : ['1.3.1', '2.4.6'],
   target_resources    : ['input[type="image"]'],
   cache_dependency    : 'controls_cache',
   mc_properties       : [],
   resource_properties : ['alt', 'aria_label', 'aria_labelledby', 'title'],
   language_dependency : "",
   validate            : function (dom_cache, rule_result) {
  
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
   
     var tag_name;
   
     var control_elements   = dom_cache.controls_cache.control_elements;
     var control_elements_len = control_elements.length;
       
     // Check to see if valid cache reference
     if (control_elements && control_elements_len) {
     
       for (var i = 0; i < control_elements_len; i++) {
         var ce = control_elements[i];
         var de = ce.dom_element;
  
         var type = control_elements[i].type;
     
         if (type === 'image') {
      
           if (de.computed_style.is_visible_to_at == OpenAjax.a11y.VISIBILITY.VISIBLE) {
     
             if (ce.computed_label) {
               if (ce.computed_label.length) {
                 rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', [type.toUpperCase()]);
               }
               else {
                 rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_2', [type.toUpperCase()]);                    
               }
             }
             else {
               rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', [type.toUpperCase()]);     
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [type.toUpperCase()]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
 },
 
/**
 * @object CONTROL_3
 *
 * @desc Groups of radio buttons should be contained in fieldset/legend
 */
{ rule_id             : 'CONTROL_3', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '3.3.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],
  target_resources    : ['input[type="radio"]'],
  cache_dependency    : 'controls_cache',
  mc_properties       : ['accessible_name'],
  resource_properties : ['fieldset_element', 'aria_label', 'aria_labelledby'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
   
     var control_elements   = dom_cache.controls_cache.control_elements;
     var control_elements_len = control_elements.length;
       
     // Check to see if valid cache reference
     if (control_elements && control_elements_len) {
     
       for (var i = 0; i < control_elements_len; i++) {
         var ce = control_elements[i];
         var de = ce.dom_element;
  
         var type = control_elements[i].control_type;
     
         if (type == OpenAjax.a11y.CONTROL_TYPE.RADIO) {
      
           if (de.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
     
             if (ce.fieldset_element) {
               if (ce.fieldset_element.legend_element && 
                   ce.fieldset_element.legend_element.computed_label &&
                   ce.fieldset_element.legend_element.computed_label.length) {
                 rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', []);
               }
               else {
                 rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', []);               
               }
             }
             else {  
               if (de.aria_labelledby && de.aria_labelledby.length) {
                   rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ce, 'MANUAL_CHECK_1', []);     
               }
               else {
                 if (de.aria_label && de.aria_label.length) {
                   rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ce, 'MANUAL_CHECK_2', []);     
                 }
                 else {
                   rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_2', []);
                 }    
               }
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', []);     
           }
         }
       } // end loop
     }   
  } // end validate function
},

/**
 * @object CONTROL_4
 *
 * @desc Button elements must have text content and input type button must have a value attribute with content
 */
{ rule_id             : 'CONTROL_4', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '3.3.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],
  target_resources    : ['button'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['computed_label'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY = OpenAjax.a11y.VISIBILITY;
      
     var control_elements   = dom_cache.controls_cache.control_elements;
     var control_elements_len = control_elements.length;
       
     // Check to see if valid cache reference
     if (control_elements && control_elements_len) {
     
       for (var i = 0; i < control_elements_len; i++) {
         var ce = control_elements[i];
         var de = ce.dom_element;
  
         var type = control_elements[i].control_type;
     
         if (type == OpenAjax.a11y.CONTROL_TYPE.BUTTON) {
      
           if (de.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
     
             if (ce.computed_label && ce.computed_label.length) {
               rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', []);     
             }
             else {
               rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', []);     
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', []);     
           }
         }
       } // end loop
     }   

  } // end validate function
},
 

/**
 * @object CONTROL_5
 *
 * @desc Tests if Textarea, select, input and button elements with id attributes have unique id values on the page
 *
 * @note Do not need to test for invisible elements, since getElementById searches all elements int he DOM
 */
{ rule_id             : 'CONTROL_5', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '4.1.1',
  wcag_related_ids    : ['3.3.2', '1.3.1', '2.4.6'],
  target_resources    : ['input[type="checkbox"]', 'input[type="radio"]', 'input[type="text"]', 'input[type="password"]', 'input[type="file"]', 'select', 'textarea'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['id'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var ID          = OpenAjax.a11y.ID;
   
    var control_elements      = dom_cache.controls_cache.control_elements;
    var control_elements_len  = control_elements.length;
       
    // Check to see if valid cache reference
    if (control_elements && control_elements_len) {
     
      for (var i = 0; i < control_elements_len; i++) {
        var ce = control_elements[i];
        var de = ce.dom_element;
        
        if (de.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
        
          switch (de.id_unique) { 
        
          case ID.NOT_UNIQUE:
            rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', [de.id]);
            break;          
          
          case ID.UNIQUE:
            rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', [de.id]);               
            break;
          
          default:
            break;       
            
          } // end switch
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [de.tag_name]);             
        }
      } // end loop
    }     
  } // end validate function
},
 
/**
 * @object CONTROL_6
 * 
 * @desc Label element with a for attribute reference does not reference a form control
 */
{ rule_id             : 'CONTROL_6', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '3.3.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],
  target_resources    : ['label'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['for'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY = OpenAjax.a11y.VISIBILITY;
   
    var label_elements      = dom_cache.controls_cache.label_elements;
    var label_elements_len  = label_elements.length;
       
    // Check to see if valid cache reference
    if (label_elements && label_elements_len) {
     
      for (var i = 0; i < label_elements_len; i++) {
        var le = label_elements[i];
        
        if (le.unused_label) {
          rule_result.addResult(TEST_RESULT.FAIL, le, 'ACTION_1', [le.for_id]);
        }        
      } // end loop
    }     
  } // end validate function
},

/** 
 * @object CONTROL_7
 *
 * @desc Label or legend element should contain content 
 */
 
{ rule_id             : 'CONTROL_7', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  cache_dependency    : 'controls_cache',
  wcag_primary_id     : '3.3.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],
  target_resources    : ['label', 'legend'],
  resource_properties    : ['computed_label'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY = OpenAjax.a11y.VISIBILITY;
   
    var label_elements      = dom_cache.controls_cache.label_elements;
    var label_elements_len  = label_elements.length;
    
    // Check to see if valid cache reference
    if (label_elements && label_elements_len) {
     
      for (var i = 0; i < label_elements_len; i++) {
        var le = label_elements[i];

        var lde = le.dom_element;
        
        var ce = le.control_element;
        
        if (VISIBILITY.VISIBLE || le.hidden_label) {

          if (ce) {

            var cde = ce.dom_element;

            if (cde.computed_style.is_visible_to_at) {

              if (le.computed_label && le.computed_label.length === 0) {
                rule_result.addResult(TEST_RESULT.FAIL, le, 'ACTION_1', [lde.tag_name]);
              }
              else {
                rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [lde.tag_name]);        
              }
            }
            else {
              rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_2', [ce.control_type, lde.tag_name]);                
            }
          }  
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [lde.tag_name]);                        
        }
        
      } // end loop
    } 
  } // end validate function
},


/** 
 * @object CONTROL 8
 *
 * @desc Fieldset should contain exactly one legend element 
 */
 
{ rule_id             : 'CONTROL_8', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '3.3.2',
  wcag_related_ids    : ['1.3.1', '2.4.6', '4.1.1'],
  target_resources    : ['fieldset'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['legend_count'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY = OpenAjax.a11y.VISIBILITY;
   
    var fieldset_elements      = dom_cache.controls_cache.fieldset_elements;
    var fieldset_elements_len  = fieldset_elements.length;
       
    // Check to see if valid cache reference
    if (fieldset_elements && fieldset_elements_len) {
     
      for (var i = 0; i < fieldset_elements_len; i++) {
        var fe = fieldset_elements[i];
        var de = fe.dom_element;

        if (de.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {

          if (fe.legend_count === 0 || !fe.legend_element ) {
            rule_result.addResult(TEST_RESULT.FAIL, fe, 'ACTION_1', []);        
          }
          else {
            if (fe.legend_count > 1) {
              rule_result.addResult(TEST_RESULT.FAIL, fe, 'ACTION_2', [(fe.legend_count-1)]);        
            }
            else {
              de = fe.legend_element.dom_element;
              
              if (de.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
                rule_result.addResult(TEST_RESULT.PASS, fe, 'PASS_1', []);                
              }
              else {
                rule_result.addResult(TEST_RESULT.FAIL, fe, 'ACTION_3', []);                                  
              }
            }
          }
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, fe, 'HIDDEN_1', []);                          
        }
      } // end loop
    } 

  } // end validate function
},

/** 
 * @object CONTROL_9
 *
 * @desc Form controls should not be labelled using the TITLE attribute 
 */
 
{ rule_id             : 'CONTROL_9', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '3.3.2',
  wcag_related_ids    : ['4.1.1'],
  target_resources    : ['input', 'select', 'textarea'],
  cache_dependency    : 'controls_cache',
  mc_properties       : ['title'],
  resource_properties : ['title'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY = OpenAjax.a11y.VISIBILITY;

    var control_elements      = dom_cache.controls_cache.control_elements;
    var control_elements_len  = control_elements.length;
       
    // Check to see if valid cache reference
    if (control_elements && control_elements_len) {
     
      for (var i = 0; i < control_elements_len; i++) {
        var ce = control_elements[i];
        var de = ce.dom_element;
        
        if (de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {

          if (ce.computed_label_source === OpenAjax.a11y.SOURCE.TITLE_ATTRIBUTE) {
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ce, 'MANUAL_CHECK_1', [de.tag_name]);        
          }
          else {
            rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', []);                  
          }  
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [de.tag_name]);                          
        }
        
      } // end loop
    } 

  } // end validate function
},

/**
 * @object CONTROL_10
 * 
 * @desc Accessible labels must be unique for every textarea, 
 *       select and input element of type text, password, radio, 
 *       and checkbox on a page
 */
 
{ rule_id             : 'CONTROL_10', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '2.4.6',
  wcag_related_ids    : ['1.3.1', '3.3.2'],
  target_resources    : ['input[type="checkbox"]', 'input[type="radio"]', 'input[type="text"]', 'input[type="password"]', 'input[type="file"]', 'select', 'textarea'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['computed_label', 'fieldset_element', 'computed_label_source', 'name_attribute'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY = OpenAjax.a11y.VISIBILITY;
   
    var control_elements   = dom_cache.controls_cache.control_elements;
    var control_elements_len = control_elements.length;
    var ces   = [];

    // Check to see if valid cache reference
    if (control_elements && control_elements_len) {
     
      // collect all the visible controls 
      for (var i = 0; i < control_elements_len; i++) {
        var ce = control_elements[i];
        var de = ce.dom_element;
  
        var control_type = ce.control_type;

        if (control_type === OpenAjax.a11y.CONTROL_TYPE.CHECKBOX ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.FILE     ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.PASSWORD ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.RADIO    ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.SELECT   ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.TEXT     ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.TEXTAREA ) {

          if (de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {  
            if (ce.computed_label && ce.computed_label.length) {
              ces.push(ce);
            }
            else {
              rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', [ce.type.toUpperCase()]);                        
            }
          }
          else {
            rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [ce.type.toUpperCase()]);                                    
          }
        }
      } // end loop    
      
      // sort labels

      ces = dom_cache.sortArrayOfObjects(ces,'computed_label_for_comparison', true); 

      for (i = 0; i < ces.length; i++) {
        ce = ces[i];

        if (ce.duplicate) {
          rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_2', []);                
        }
        else {
          rule_result.addResult(TEST_RESULT.PASS, ce, 'PASS_1', []);        
        }
      }
      
    } 
  } // end validate function
},

/**
 * @object CONTROL_11
 * 
 * @desc If there is more than one form on page, input element of type 
 *       submit and reset must have unique labels in each form using the value attribute
 * 
 */
 
{ rule_id             : 'CONTROL_11', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '2.4.6',
  wcag_related_ids    : ['1.3.1', '3.3.2'],
  target_resources    : ['input[type="submit"]', 'input[type="reset"]'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['computed_label', 'value'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
   
    var i;
    var ce;
    var tag_name;
    var type;
   
    var control_elements   = dom_cache.controls_cache.control_elements;
    var control_elements_len = control_elements.length;
    var current_controls   = [];
       
  } // end validate function

},

/**
 * @object CONTROL_12
 * 
 * @desc Form control label should describe the purpose of the form control
 * 
 */
 
{ rule_id             : 'CONTROL_12', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.FORM_CONTROLS,
  wcag_primary_id     : '2.4.6',
  wcag_related_ids    : ['1.3.1', '3.3.2'],
  target_resources    : ['button', 'input[type="checkbox"]', 'input[type="radio"]', 'input[type="text"]', 'input[type="password"]', 'input[type="file"]', 'input[type="submit"]', 'input[type="reset"]', 'select', 'textarea'],
  cache_dependency    : 'controls_cache',
  mc_properties       : ['computed_label'],
  resource_properties : ['computed_label', 'computed_label_source'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY = OpenAjax.a11y.VISIBILITY;
   
    var control_elements   = dom_cache.controls_cache.control_elements;
    var control_elements_len = control_elements.length;
    var ces   = [];

    // Check to see if valid cache reference
    if (control_elements && control_elements_len) {
     
      // collect all the visible controls 
      for (var i = 0; i < control_elements_len; i++) {
        var ce = control_elements[i];
        var de = ce.dom_element;
  
        var control_type = ce.control_type;

        if (control_type === OpenAjax.a11y.CONTROL_TYPE.BUTTON_ELEMENT ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.BUTTON_INPUT   ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.CHECKBOX       ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.FILE           ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.PASSWORD       ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.RADIO          ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.SELECT         ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.TEXT           ||
            control_type === OpenAjax.a11y.CONTROL_TYPE.TEXTAREA ) {

          if (de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) { 
          
            if (ce.computed_label && ce.computed_label.length) {
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ce, 'MANUAL_CHECK_1', [de.tag_name]);                
            }
            else {
              rule_result.addResult(TEST_RESULT.FAIL, ce, 'ACTION_1', [de.tag_name]);        
            }             
          }
          else {
            rule_result.addResult(TEST_RESULT.HIDDEN, ce, 'HIDDEN_1', [de.tag_name]);                                    
          }          
        }
      } // end loop          
    }        
  } // end validate function

}
]); 


    

/* ---------------------------------------------------------------- */
/* OpenAjax Alliance IMG and AREA element Rules                     */
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/**
 * @object IMAGE_1
 *
 * @desc Images must have a source for an accessible name
 */
 
{ rule_id             : 'IMAGE_1', 
  last_updated        : '2012-04-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
  wcag_primary_id     : '1.1.1',
  wcag_related_ids    : [],
  target_resources    : ['img', 'area', '[role="img"]'],
  cache_dependency    : 'images_cache',
  resource_properties : ['accessible_name', 'alt', 'aria_label', 'aria_labelledby', 'title', 'is_visible_to_at', 'role'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
 
    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;

    var image_elements   = dom_cache.images_cache.image_elements;
    var image_elements_len = image_elements.length;
       
    // Check to see if valid cache reference
    if (image_elements && image_elements_len) {
     
      for (var i = 0; i < image_elements_len; i++) {
        var ie = image_elements[i];
        var de = ie.dom_element;
        
//        OpenAjax.a11y.logger.debug("Image is visibile to AT: " + de.computed_style.is_visible_to_at);

        if ((de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE ) &&
            (!de.has_role || (de.role !== 'presentation'))){
          
          if (ie.accessible_name_source !== SOURCE.NONE) {
            if (ie.accessible_name_source === SOURCE.ALT_ATTRIBUTE) {
              if ((de.tag_name === "img") || (de.tag_name === "area")) rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_1', [de.tag_name]);
              else rule_result.addResult(TEST_RESULT.FAIL, ie, 'ACTION_2', [de.tag_name]); 
            } else if (ie.accessible_name_source === SOURCE.ARIA_LABELLEDBY) rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_2', [de.tag_name]);
            else if (ie.accessible_name_source === SOURCE.ARIA_LABEL)      rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_3', [de.tag_name]);
            else rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_4', [de.tag_name]);
          }
          else {
            rule_result.addResult(TEST_RESULT.FAIL, ie, 'ACTION_1', [de.tag_name]);     
          }
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', [de.tag_name]);     
        }
      } // end loop
    } 
  } // end validation function  
},

/**
 * @object IMAGE_2
 *
 * @desc If the longdesc attribute is defined, it must have valid URI
 */
{ rule_id             : 'IMAGE_2', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
  wcag_primary_id     : '1.1.1',
  wcag_related_ids    : [],

  target_resources    : ['img', '[role="img"]'],
  cache_dependency    : 'images_cache',
  resource_properties    : ['longdesc', 'longdesc_is_broken', 'is_visible_to_at'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {


    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var URL_RESULT = OpenAjax.a11y.URL_RESULT;

    var image_elements   = dom_cache.images_cache.image_elements;
    var image_elements_len = image_elements.length;
       
    // Check to see if valid cache reference
    if (image_elements && image_elements_len) {
     
      for (var i = 0; i < image_elements_len; i++) {
        var ie = image_elements[i];
        var de = ie.dom_element;
        
        if (ie.longdesc) {
        
          if ((de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) &&
              (!de.has_role || (de.role !== 'presentation'))) {  

          
              switch (ie.longdesc_is_broken) {
         
              case URL_RESULT.VALID:
                rule_result.addResult(OpenAjax.a11y.TEST_RESULT.PASS, ie, 'PASS_1', []);     
                break;
          
              case URL_RESULT.INVALID:
                rule_result.addResult(TEST_RESULT.FAIL, ie, 'ACTION_1', []);     
                break;
          
              case URL_RESULT.NOT_TESTED:
                rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ie, 'MANUAL_CHECK_1', []);     
                break;
         
              default:
                rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ie, 'MANUAL_CHECK_1', []);
                break;
              } 
          }
          else {
            rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', []);     
          }
        }      
      } // end loop
    } 
  } // end validation function
}, 

/**
 * @object IMAGE_3
 *
 * @desc The file name of the image should not be part of the accessible name content (it must have an image file extension)
 */
{ rule_id             : 'IMAGE_3', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
  wcag_primary_id     : '1.1.1',
  wcag_related_ids    : [],

  target_resources    : ['img', '[role="img"]'],
  cache_dependency    : 'images_cache',
  resource_properties    : ['accessible_name', 'file_name', 'is_visible_to_at'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;

    var image_elements   = dom_cache.images_cache.image_elements;
    var image_elements_len = image_elements.length;
       
    // Check to see if valid cache reference
    if (image_elements && image_elements_len) {
     
      for (var i = 0; i < image_elements_len; i++) {
        var ie = image_elements[i];
        var de = ie.dom_element;

        if (ie.accessible_name_for_comparison && ie.accessible_name_for_comparison.length) {
          
          if ((de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) &&
              (!de.has_role || (de.role !== 'presentation'))){
          
            // make sure it has a file extension, will assume extension is for an image
            if (ie.file_name.indexOf('.') >= 0) {
         
              if (ie.accessible_name_for_comparison.indexOf(ie.file_name) >= 0 ) {
                rule_result.addResult(TEST_RESULT.FAIL, ie, 'FAIL_MC', []);                 
              }
              else {
                rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_1', []);                 
              }
            }
            else {
              rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_1', []);                              
            }
          }
          else {
            rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', [de.tag_name]);     
          }
        }      
      } // end loop
    } 
  } // end validation function  
 }, 

/**
 * @object IMAGE_4_EN (English)
 *
 * @desc If the accessible name contains content, it should be less than 100 characters long, longer descriptions should use long description techniques (English only)
 */
{ rule_id             : 'IMAGE_4_EN', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
  wcag_primary_id     : '1.1.1',
  wcag_related_ids    : [],

  target_resources    : ['img', 'area'],
  cache_dependency    : 'images_cache',
  resource_properties    : ['accessible_name', 'accessible_name_length', 'is_visible_to_at'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var MAX_ACCESSIBLE_NAME_LENGTH = 100;

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;

    var image_elements   = dom_cache.images_cache.image_elements;
    var image_elements_len = image_elements.length;
       
    // Check to see if valid cache reference
    if (image_elements && image_elements_len) {
     
      for (var i = 0; i < image_elements_len; i++) {
        var ie = image_elements[i];
        var de = ie.dom_element;

        if ((de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) &&
            (!de.has_role || (de.role !== 'presentation'))){
          
          if (ie.accessible_name_source !== SOURCE.NONE) {
          
            if (ie.accessible_name_length > MAX_ACCESSIBLE_NAME_LENGTH) {
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ie, 'MANUAL_CHECK_1', [MAX_ACCESSIBLE_NAME_LENGTH]);     
            }
            else {     
              rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_1', [MAX_ACCESSIBLE_NAME_LENGTH]);     
            }
          }
          else {
            rule_result.addResult(TEST_RESULT.FAIL, ie, 'ACTION_1', []);               
          }
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', [de.tag_name]);     
        }
      } // end loop
    } 
  } // end validation function
},

/**
 * @object IMAGE_5
 *
 * @desc If an image has a height or width of 6 pixels its accessible name set to empty, role set to presentation or the image removed and use CSS position
 */
{ rule_id             : 'IMAGE_5', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
  wcag_primary_id     : '1.1.1',
  wcag_related_ids    : [],

  target_resources    : ['img', '[role="img"]'],
  cache_dependency    : 'images_cache',
  resource_properties    : ['accessible_name_length', 'role', 'height', 'width', 'is_visible_to_at'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var MAX_IMAGE_HEIGHT = 6;
    var MAX_IMAGE_WIDTH  = 6;

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;

    var image_elements   = dom_cache.images_cache.image_elements;
    var image_elements_len = image_elements.length;
       
    // Check to see if valid cache reference
    if (image_elements && image_elements_len) {
     
      for (var i = 0; i < image_elements_len; i++) {
        var ie = image_elements[i];
        var de = ie.dom_element;

        if (de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {
          
          if ((ie.height <= MAX_IMAGE_HEIGHT) || (ie.width <= MAX_IMAGE_WIDTH)) {
            if (de.has_role && (de.role === 'presentation')) rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_1', []);
            else if (ie.accessible_name_length === 0) rule_result.addResult(TEST_RESULT.PASS, ie, 'PASS_2', []);
            else rule_result.addResult(TEST_RESULT.FAIL, ie, 'ACTION_1', []);
          } 
        }
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', [de.tag_name]);     
        }
      } // end loop
    } 
  } // end validation function
}, 
 
/**
 * @object IMAGE_6
 *
 * @desc If the alt is empty or role is set presentation verify the image is purely decorative
 */
{ rule_id             : 'IMAGE_6', 
  last_updated        : '2011-09-16', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.IMAGES,
  wcag_primary_id     : '1.1.1',
  wcag_related_ids    : [],
  target_resources    : ['img', '[role="img"]'],
  cache_dependency    : 'images_cache',
  resource_properties : ['accessible_name', 'role', 'is_visible_to_at'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
    
    var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY    = OpenAjax.a11y.VISIBILITY;

    var image_elements   = dom_cache.images_cache.image_elements;
    var image_elements_len = image_elements.length;

    // Check to see if valid cache reference
    if (image_elements && image_elements_len) {
     
      for (var i = 0; i < image_elements_len; i++) {
        var ie = image_elements[i];
        var de = ie.dom_element;

        if ((de.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) &&
            (!de.has_role || (de.role !== 'presentation'))) {
            
          if (ie.accessible_name_length === 0 || ie.is_presentation) {     
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, ie, 'MANUAL_CHECK_1', []);
          }
          
        }    
        else {
          rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_1', []);     
        }
      } // end loop
    } 
  } // end validation function
} 
]); 
 


    

/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Heading and Landmark Rules                    */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/**
 * @object HEADING_1
 *
 * @desc Page contains at least one H1 element and each H1 element has content
 *       If there are main landmarks the H1 elements are children of the main landmarks
 */               
{ rule_id             : 'HEADING_1', 
  last_updated        : '2012-06-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.1',
  wcag_related_ids    : ['1.3.1', '2.4.2', '2.4.6', '2.4.10'],
  target_resources    : ['h1'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name', 'name_length'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
 
      var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
  
      var h1_elements     = dom_cache.headings_landmarks_cache.h1_elements;
      var h1_elements_len = h1_elements.length;
      
      var page_element = dom_cache.headings_landmarks_cache.page_element;
      
      var h1_count = 0;
      
      if (h1_elements && h1_elements_len) {
      
        for (var i = 0; i < h1_elements_len; i++ ) {
          var he = h1_elements[i];

          if (he.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
            rule_result.addResult(TEST_RESULT.HIDDEN, he, 'HIDDEN_1', []);                      
          }
          else {
            if (he.name && he.name.length) {
              rule_result.addResult(TEST_RESULT.PASS, he, 'PASS_1', []);
              h1_count++;
            }
            else {
              rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_2', []);
            }
          }  
        }
      }

     if (page_element) {
       // Test if no h1s
       if (h1_count === 0) rule_result.addResult(TEST_RESULT.FAIL, page_element, 'ACTION_1', []);
       else rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_1', []);
     } 
  } // end validate function
}, 

/**
 * @object HEADING_2
 *
 * @desc If there are main landmarks and H1 elements, H1 elements should be children of main landmarks 
 *
 */               
{ rule_id             : 'HEADING_2', 
  last_updated        : '2012-06-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.6',
  wcag_related_ids    : ['1.3.1', '2.4.1', '2.4.2', '2.4.10'],
  target_resources    : ['h1'],
  cache_dependency    : 'headings_landmarks_cache',
  
  resource_properties    : ['tag_name', 'id', 'name', 'main_landmark'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var h1_elements     = dom_cache.headings_landmarks_cache.h1_elements;
    var h1_elements_len = h1_elements.length;

    var main_elements     = dom_cache.headings_landmarks_cache.main_elements;
    var main_elements_len = main_elements.length;

    if (main_elements && h1_elements && main_elements_len && h1_elements_len) {
      
      for (var i = 0; i < h1_elements_len; i++) {
        var he = h1_elements[i];
        var de = he.dom_element;
        
        if (de.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
          rule_result.addResult(TEST_RESULT.HIDDEN, he, 'HIDDEN_1', []);                      
        }
        else {
          if (he.is_child_of_main) rule_result.addResult(TEST_RESULT.PASS, he, 'PASS_1', []);
          else rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_1', []);
        }
        
      }
    }
  } // end validate function
},

/**
 * @object HEADING_3
 *
 * @desc Sibling headings of the same level that share the same parent heading should be unique
 *       This rule applies only when there are no main landmarks on the page and at least one 
 *       sibling heading
 *
 */               
{ rule_id             : 'HEADING_3', 
  last_updated        : '2012-06-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.6',
  wcag_related_ids    : ['1.3.1', '2.4.10'],
  target_resources    : ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    function getSiblingHeadings(index, heading_element) {
   
      var list = [];
      var flag = true; 
      
      tested_list.push(heading_element);
                  
      for (var i = (index+1); i < heading_elements_len; i++) {
        
        var he = heading_elements[i];
        
        if (he.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
          tested_list.push(he);
          continue;
        }
        
        if (heading_element.level > he.level) return list;
        
        if (heading_element.level === he.level) {
          if (flag) list.push(heading_element);
          flag = false;
          
          list.push(he);
          tested_list.push(he);
        }  

      }
        
      if (list.length > 1) return list;
      else return[];
   
    }

    function notInTestedList(he) {
    
      for (var i = 0; i < tested_list.length; i++) {
        if (tested_list[i] === he) return false;
      }
    
      return true;
    }

    function notInDoneList(he) {
    
      for (var i = 0; i < done_list.length; i++) {
        if (done_list[i] === he) return false;
      }
    
      return true;
    }


    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var heading_elements     = dom_cache.headings_landmarks_cache.heading_elements;
    var heading_elements_len = heading_elements.length;

    var main_elements     = dom_cache.headings_landmarks_cache.main_elements;
    var main_elements_len = main_elements.length;
    
    var tested_list = [];
    var done_list   = [];
    var i, j, k;
    var sibling_headings = [];
    var sibling_headings_len = 0;

//    if (main_elements_len === 0 && heading_elements_len > 1) {
    if (heading_elements_len > 1) {
    
      for (i = 0; i < (heading_elements_len-1); i++) {
      
        var he = heading_elements[i];
        
        if (notInTestedList(he)) sibling_headings = getSiblingHeadings(i, he);
        
        sibling_headings_len = sibling_headings.length;
      
        if (sibling_headings_len > 1) {
        
          for (j = 0; j < (sibling_headings_len-1); j++) {
          
            var sh1 = sibling_headings[j];
            var first_flag = true;
            
            if (notInDoneList(sh1) && sh1.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {
            
              for (k = j+1; k < sibling_headings_len; k++) {
                var sh2 = sibling_headings[k];
                
                if (sh1.name_for_comparison === sh2.name_for_comparison) {
                  if (first_flag) { 
                    rule_result.addResult(TEST_RESULT.FAIL, sh1, 'ACTION_1', [sh1.dom_element.tag_name]); 
                    done_list.push(sh1);
                  }  
                  rule_result.addResult(TEST_RESULT.FAIL, sh2, 'ACTION_1', [sh2.dom_element.tag_name]);
                  done_list.push(sh2);
                  first_flag = false;
                }
              }      
            }  
          }
          
          for (j = 0; j < sibling_headings_len; j++) {
            var sh = sibling_headings[j];
            if (notInDoneList(sh)) { 
              if (sh.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {
                rule_result.addResult(TEST_RESULT.PASS, sh, 'PASS_1', [sh.dom_element.tag_name]);
              }
              else {
                rule_result.addResult(TEST_RESULT.HIDDEN, sh, 'HIDDEN_1', [sh.dom_element.tag_name]);              
              }
              done_list.push(sh);
            }  
          }
        }
      }
    }
  } // end validate function
},

/**
 * @object HEADING_4
 *
 * @desc Headings should describe the content of the section they label
 *
 */               
{ rule_id             : 'HEADING_4', 
  wcag_primary_id     : '2.4.6',
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  last_updated        : '2012-06-31', 
  wcag_related_ids    : ['1.3.1', '2.4.10'],
  target_resources    : ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var heading_elements     = dom_cache.headings_landmarks_cache.heading_elements;
    var heading_elements_len = heading_elements.length;

    for (var i = 0; i < heading_elements_len; i++ ) {
      var he = heading_elements[i];
      var de = he.dom_element;
      if (de.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, he, 'HIDDEN_1', [de.tag_name]);                      
      }
      else {
        if (he.name.length) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, he, 'MANUAL_CHECK_1', [de.tag_name]);
        else rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_1', [de.tag_name]);
      }  
    }
  } // end validate function
},

/**
 * @object HEADING_5
 *
 * @desc Headings must be properly nested
 *
 */               
{ rule_id             : 'HEADING_5', 
  last_updated        : '2012-11-01', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.6',
  wcag_related_ids    : ['1.3.1', '2.4.10'],
  target_resources    : ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties : ['tag_name', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     function getNextVisibleHeadingWithContent(flag) {
     
       if (typeof flag !== 'boolean' || flag) heading_index++;
     
       while (heading_index < heading_elements_len) {
       
         var he = heading_elements[heading_index];
         var de = he.dom_element;
         var vf = de.computed_style.is_visible_to_at;
         
         if ((vf === VISIBILITY.VISIBLE) && 
             (he.name.length > 0)) {
           return he;
         }
         
         if (vf === VISIBILITY.HIDDEN) {
           rule_result.addResult(TEST_RESULT.HIDDEN, he, 'HIDDEN_1', [de.tag_name]); 
         }
         else {
           rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_2', [de.tag_name]);          
         }
         
         heading_index++;          
         
       }
       
       return null;
     }

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var heading_elements     = dom_cache.headings_landmarks_cache.heading_elements;
    var heading_elements_len = heading_elements.length;
    var heading_index = 0;
    var heading_fail = 0;

    // Check to see if any landmarks are not in a landmark
    
    var flag = true;
    
    for (var i = 0; (i < heading_elements_len) && flag; i++) {
      
      var he = heading_elements[i];
      var cs = he.dom_element.computed_style;
      
      if ((cs.is_visible_to_at === VISIBILITY.VISIBLE) &&
          (typeof he.parent_landmark !== 'object')) {      
        flag = false;      
      }
    }

//    OpenAjax.a11y.logger.debug("Heading 5 Rule - Total: " + heading_elements_len + " Index: " + i + " Flag: " + flag);

    // If all headings are in a landmark do not evaluate this rule
    if (flag) return;

    var page_element = dom_cache.headings_landmarks_cache.page_element;

    var he_last = getNextVisibleHeadingWithContent(false);
    
    if (he_last && page_element) {

      var de = he_last.dom_element;
      
      rule_result.addResult(TEST_RESULT.PASS, he_last, 'PASS_1', [de.tag_name]);

      he = getNextVisibleHeadingWithContent();
      
      while (he) {
      
        de = he.dom_element;
        
        if (he.level <= (he_last.level + 1) ) {
          rule_result.addResult(TEST_RESULT.PASS, he, 'PASS_1', [de.tag_name]);
        }
        else {
          rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_1', [de.tag_name]); 
          heading_fail++;
        }
        
        he_last = he;
        he = getNextVisibleHeadingWithContent();
      }

//      if (heading_fail === 0) rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_2', []);
//      else if (heading_fail === 1) rule_result.addResult(TEST_RESULT.FAIL, page_element, 'ACTION_3', []);
//      else rule_result.addResult(TEST_RESULT.FAIL, page_element, 'ACTION_4', [heading_fail]);
    }
  } // end validate function
},

/**
 * @object HEADING_6
 *
 * @desc Headings should not consist only of image content
 *
 */               
{ rule_id             : 'HEADING_6', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  last_updated        : '2012-06-31', 
  wcag_primary_id     : '1.3.1',
  wcag_related_ids    : ['2.4.6', '2.4.10'],
  target_resources    : ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var heading_elements     = dom_cache.headings_landmarks_cache.heading_elements;
    var heading_elements_len = heading_elements.length;

    for (var i = 0; i < heading_elements_len; i++ ) {
      var  he = heading_elements[i];
      var de = he.dom_element;
      if (de.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, he, 'HIDDEN_1', [de.tag_name]);                      
      }
      else {
        if (he.text_only_from_image) rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_1', [de.tag_name]);
        else rule_result.addResult(TEST_RESULT.PASS, he, 'PASS_1', [de.tag_name]);
      }  
    }
  } // end validate function
}
]); 


    

/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Control Rules                                 */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([

/**
 * @object KEYBOARD_1
 * 
 * @desc Widget elements on non-interactive elements or that override the default role of an interactive element 
 *       need keyboard event handlers on the widget element or a parent element of the widget
 */
     
{ rule_id             : 'KEYBOARD_1', 
  last_updated        : '2012-12-04', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT,
  wcag_primary_id     : '2.1.1',
  wcag_related_ids    : ['4.1.2'],

  target_resources    : ['widgets'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['role', 'tab_index', 'is_owned', 'has_key_down', 'has_key_press', 'has_key_up', 'ancestor_has_key_down', 'ancestor_has_key_press', 'ancestor_has_key_up'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     function checkForKeyboardOwner(we) {

       if (we.is_owned && (we.owner_controls.length === 1)) {
       
         var owner = we.owner_controls[0];
         var de = owner.dom_element;
         
         if (de.has_activedescendant) {
           var has_keyboard_support = de.events.has_key_down;
           has_keyboard_support = has_keyboard_support || de.events.has_key_press;
           has_keyboard_support = has_keyboard_support || de.events.has_key_up;
           return has_keyboard_support;
         }
       }
     
       return false;
     }
  
     function checkForKeyboardOnRequiredChildren(widget) {

       function checkChildren(children) {
       
         if (typeof children !== 'object' || !children.length) return false;
       
         var flag = false;
         var children_len = children.length;
         
         for (var i = 0; (i < children_len) && !flag; i++) {
         
           var we = children[i];
           var de = we.dom_element;

//           OpenAjax.a11y.logger.debug("  KEYBOARD RULE 1 CHID: " + de.role + " ("+ we.toString() + ")");

           flag = de.events.has_key_down || de.events.has_key_press || de.events.has_key_up;

           if (!flag && de.role_info.reqChildren && de.role_info.reqChildren.length) { 
             flag = checkChildren(we.child_cache_elements); 
           }
         }
         return flag;
       }

       return checkChildren(widget.child_cache_elements);
       
     }  

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;

//         OpenAjax.a11y.logger.debug("  KEYBOARD RULE 1: " + de.role + " ("+ we.toString() + ")");

         if (de.role_info.roleType === 'widget') {

           if (style.is_visible_to_at === VISIBILITY.VISIBLE) {
           
             var has_keyboard_support = de.events.has_key_down;
             has_keyboard_support = has_keyboard_support || de.events.has_key_press;
             has_keyboard_support = has_keyboard_support || de.events.has_key_up;
             
             if (de.role_info.supportOnClick) {
               has_keyboard_support = has_keyboard_support || de.events.has_click;
               has_keyboard_support = has_keyboard_support || de.events.has_double_click;
             }  

             if (has_keyboard_support) {
               rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_1', [we]);     
             }
             else {
      
               has_keyboard_support = de.events.ancestor_has_key_down;
               has_keyboard_support = has_keyboard_support || de.events.ancestor_has_key_press;
               has_keyboard_support = has_keyboard_support || de.events.ancestor_has_key_up;
               
               if (de.role_info.supportOnClick) {
                 has_keyboard_support = has_keyboard_support || de.events.ancestor_has_click;
                 has_keyboard_support = has_keyboard_support || de.events.ancestor_has_double_click;
               }  
               
               if (has_keyboard_support) { 
                 rule_result.addResult(TEST_RESULT.MANUAL_CHECK, we, 'MANUAL_CHECK_1', [de.role]);
               }  
               else {
                 
                 if (checkForKeyboardOwner(we)) {
                   rule_result.addResult(TEST_RESULT.MANUAL_CHECK, we, 'MANUAL_CHECK_2', [de.role, we.owner_controls[0].toString()]);
                 }
                 else {
                   if (checkForKeyboardOnRequiredChildren(we)) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, we, 'MANUAL_CHECK_3', [de.role]);
                   else rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_1', [de.role]);
                 }
               }  
             }             
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, we, 'HIDDEN_1', [we.toString()]);     
           }
         }  
       } // end loop
     } 
   } // end validation function   
   
},

/**
 * @object KEYBOARD_2
 * 
 * @desc Elements with widget roles have tabindex defined or are a child of an element
 *       with aria-activedescendant
 */
     
{ rule_id             : 'KEYBOARD_2', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT,
  last_updated        : '2012-12-04', 
  wcag_primary_id     : '2.1.1',
  wcag_related_ids    : ['4.1.2'],

  target_resources    : ['widgets'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['role', 'tab_index', 'aria_activedescendant', 'ancestor_has_activedescendant'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {


     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;

//         OpenAjax.a11y.logger.debug("  KEYBOARD RULE 2: " + de.role + " ("+ we.toString() + ")");

         if (de.role_info.roleType === 'widget') {

           if (style.is_visible_to_at === VISIBILITY.VISIBLE) {

             if (de.is_interactive_element) {
               rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_1', [de.role, de.tag_name]);     
             }
             else {
               if (!isNaN(de.tab_index)) { 
                 rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_2', [we]);     
               }  
               else {
                 if (de.ancestor_has_activedescendant) {
                   if (de.id_unique === OpenAjax.a11y.ID.UNIQUE ) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, we, 'MANUAL_CHECK_1', [de.tag_name, de.id]);
                   else rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_1', [de.tag_name]);               
                 }
                 else { 
                   rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_2', [de.tag_name]);               
                 }
               }  
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, we, 'HIDDEN_1', [we.role]);     
           }
         }  
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object KEYBOARD_3
 * 
 * @desc All operations available through the keyboard
 */
     
{ rule_id             : 'KEYBOARD_3', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT,
  last_updated        : '2013-07-03', 
  wcag_primary_id     : '2.1.1',
  wcag_related_ids    : ['2.1.2', '2.4.3',  '2.4.7', '3.2.1'],
  target_resources    : ['applet', 'object', 'widgets'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['tabindex'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var page_element = dom_cache.keyboard_focus_cache.page_element;  

//     OpenAjax.a11y.logger.debug(" Page Element: " + page_element + "  " + page_element.dom_element);

     var widget_elements      = dom_cache.controls_cache.widget_elements;
     var widget_elements_len  = widget_elements.length;
     
     var object_elements      = dom_cache.media_cache.object_elements;
     var object_elements_len  = object_elements.length;

     var widget_count = 0;

     for (var i = 0; i < widget_elements_len; i++) {
     
       var ie = widget_elements[i];
       
       var de = ie.dom_element;
       if (!de) de =ie;
       
       var cs = de.computed_style;
       
       if ((cs.is_visible_to_at    === VISIBILITY.VISIBLE) ||
           (cs.is_visible_onscreen === VISIBILITY.VISIBLE)) {
           
         widget_count++;
         
         rule_result.addResult(TEST_RESULT.PAGE, ie, 'PAGE_1', [de.tag_name, de.role]);
         
       }     
       else {
         rule_result.addResult(TEST_RESULT.HIDDEN, ie, 'HIDDEN_2', [de.tag_name]);                
       }
     }  // endfor
     
     var object_count = 0;

     for (i = 0; i < object_elements_len; i++) {
     
       var oe = object_elements[i];
       
       de = oe.dom_element;
       if (!de) de =ie;
       
       cs = de.computed_style;
       
       if ((cs.is_visible_to_at    === VISIBILITY.VISIBLE) ||
           (cs.is_visible_onscreen === VISIBILITY.VISIBLE)) {
           
         object_count++;
         
         rule_result.addResult(TEST_RESULT.PAGE, oe, 'PAGE_2', [oe.tag_name]);
         
       }     
       else {
         rule_result.addResult(TEST_RESULT.HIDDEN, oe, 'HIDDEN_2', [oe.tag_name]);                
       }
     }  // endfor

 //    OpenAjax.a11y.logger.debug(" Visible count: " + visible_count + "  Tab count: " + tab_count);
 
     if ((widget_count > 1) | (object_count > 1)) { 
 
       if (object_count === 0) {
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', [widget_count]);
       }
       else {
         if (widget_count === 0) {
           rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_2', [object_count]);
         }
         else {
           rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_3', [widget_count, object_count]);
         }
       }  
     }


   } // end validation function   
},

/**
 * @object KEYBOARD_4
 * 
 * @desc No keyboard trap
 */
     
{ rule_id             : 'KEYBOARD_4', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.KEYBOARD_SUPPORT,
  last_updated        : '2013-07-03', 
  wcag_primary_id     : '2.1.2',
  wcag_related_ids    : ['2.1.1', '2.4.3',  '2.4.7', '3.2.1'],
  target_resources    : ['object', 'applet'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tabindex'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
//     OpenAjax.a11y.logger.debug(" Page Element: " + page_element + "  " + page_element.dom_element);

     var object_elements      = dom_cache.media_cache.object_elements;
     var object_elements_len  = object_elements.length;


     for (i = 0; i < object_elements_len; i++) {
     
       var oe = object_elements[i];
       
       de = oe.dom_element;
       if (!de) de =ie;
       
       cs = de.computed_style;
       
       if ((cs.is_visible_to_at    === VISIBILITY.VISIBLE) ||
           (cs.is_visible_onscreen === VISIBILITY.VISIBLE)) {
           
         rule_result.addResult(TEST_RESULT.MANUAL_CHECK, oe, 'MANUAL_CHECK_1', [oe.tag_name]);
         
       }     
       else {
         rule_result.addResult(TEST_RESULT.HIDDEN, oe, 'HIDDEN_1', [oe.tag_name]);                
       }
     }  // endfor

   } // end validation function   
}


]); 


    

/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Heading and Landmark Rules                    */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/**
 * @object LANDMARK_1
 *
 * @desc Each page should have at least one main landmark
 *
 */               
{ rule_id             : 'LANDMARK_1', 
  last_updated        : '2012-07-14', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.1',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['[role="main"]'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'role', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var main_elements     = dom_cache.headings_landmarks_cache.main_elements;
    var main_elements_len = main_elements.length;

    var page_element = dom_cache.headings_landmarks_cache.page_element;

    var main_count = 0;

    for (var i = 0; i < main_elements_len; i++ ) {
      var me = main_elements[i];
      if (me.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, me, 'HIDDEN_1', []);                      
      }
      else {
        main_count++;
      }  
    }

    if (page_element) {
      // Test if no h1s
      if (main_count === 0) rule_result.addResult(TEST_RESULT.FAIL, page_element, 'ACTION_1', []);
      else if (main_count === 1) rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_1', []);
      else rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_2', [main_count]);
    } 
    
  } // end validate function
},

/**
 * @object LANDMARK_2
 *
 * @desc All rendered content should be contained in a landmark
 */               
{ rule_id             : 'LANDMARK_2', 
  last_updated        : '2012-07-14', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '1.3.1',
  wcag_related_ids    : ['2.4.1', '2.4.6', '2.4.10'],

  target_resources    : ['all'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'parent_landmark'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
    // If no main landmark on the page do not apply this rule
    if (dom_cache.headings_landmarks_cache.main_elements.length === 0) return;

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var elements_with_content    = dom_cache.headings_landmarks_cache.elements_with_content;
    var elements_with_content_len = elements_with_content.length;
    
    var tag_name = "";

    for (var i = 0; i < elements_with_content_len; i++ ) {
      var de =elements_with_content[i];
      
      if (de.tag_name) tag_name = de.tag_name;
      else tag_name = de.parent_element.tag_name;

//      OpenAjax.a11y.logger.debug("  Content: " + de.toString()  +  " " + de.may_have_renderable_content);
      
      if (de.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [tag_name]);                      
      }
      else {
        if (!de.parent_landmark) {
//          de.parent_landmark.addToElementCount(1);
//          rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [tag_name, de.parent_landmark.role]);
//        }
//        else {
          if (de.may_have_renderable_content) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, de, 'MANUAL_CHECK_1', [tag_name]);
          else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [tag_name]);
        }  
      }  
    }    
    
    var landmark_elements     = dom_cache.headings_landmarks_cache.landmark_elements;
    var landmark_elements_len = landmark_elements.length;
    
    for (i = 0; i < landmark_elements_len; i++ ) {
      
      var le    = landmark_elements[i];
      var role  = le.role;
      var count = le.getElementsWithContentCount();
      
      if (count) rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [role, count]);
      else rule_result.addResult(TEST_RESULT.MANUAL_CHECK, le, 'MANUAL_CHECK_2', [role]);
    }
    
  } // end validate function
},

/**
 * @object LANDMARK_3
 *
 * @desc Each page should have at least one navigation landmark
 *
 */               
{ rule_id             : 'LANDMARK_3', 
  last_updated        : '2012-07-14', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.1',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['[role="navigation"]'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties : ['tag_name', 'role', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var landmark_elements     = dom_cache.headings_landmarks_cache.landmark_elements;
    var landmark_elements_len = landmark_elements.length;

    var page_element = dom_cache.headings_landmarks_cache.page_element;

    var landmark_count = 0;
    
    var LANDMARK_ROLE = 'navigation';

    for (var i = 0; i < landmark_elements_len; i++ ) {
      var le = landmark_elements[i];
      var tag_name = le.dom_element.tag_name;
      
      if (le.role === LANDMARK_ROLE) {
        if (le.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {
          landmark_count++;
          rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [tag_name]);
        }  
      }  
    }

    if (page_element) {
      // Test if no navigation landmarks
      if (landmark_count === 0) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', []);
      else rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_1', [landmark_count]);
    } 
    
  } // end validate function
},

/**
 * @object LANDMARK_4
 *
 * @desc Each page should have at least one banner landmark
 *
 */               
{ rule_id             : 'LANDMARK_4', 
  last_updated        : '2012-07-14', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.1',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['[role="navigation"]'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'role', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var landmark_elements     = dom_cache.headings_landmarks_cache.landmark_elements;
    var landmark_elements_len = landmark_elements.length;

    var page_element = dom_cache.headings_landmarks_cache.page_element;

    var landmark_count = 0;
    
    var LANDMARK_ROLE = 'banner';

    for (var i = 0; i < landmark_elements_len; i++ ) {
      var le = landmark_elements[i];
      var tag_name = le.dom_element.tag_name;
      
      if (le.role === LANDMARK_ROLE) {
        if (le.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
          rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [tag_name]);                      
        }
        else {
          landmark_count++;
          rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [tag_name]);
        }  
      }  
    }

    if (page_element) {
      // Test if no navigation landmarks
      if (landmark_count === 0) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', []);
      else if (landmark_count === 1) rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_1', []);
      else rule_result.addResult(TEST_RESULT.FAIL, page_element, 'ACTION_1', []);
    } 
    
  } // end validate function
},

/**
 * @object LANDMARK_5
 *
 * @desc Each page should have at least one content information landmark
 *
 */               
{ rule_id             : 'LANDMARK_5', 
  last_updated        : '2012-07-14', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.1',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['[role="navigation"]'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'role', 'name'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var landmark_elements     = dom_cache.headings_landmarks_cache.landmark_elements;
    var landmark_elements_len = landmark_elements.length;

    var page_element = dom_cache.headings_landmarks_cache.page_element;

    var landmark_count = 0;
    
    var LANDMARK_ROLE = 'banner';
    
    for (var i = 0; i < landmark_elements_len; i++ ) {
      var le = landmark_elements[i];
      var tag_name = le.dom_element.tag_name;
      if (le.role === LANDMARK_ROLE) {
        if (le.dom_element.computed_style.is_visible_to_at === VISIBILITY.HIDDEN) {
          rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [tag_name, le.role]);                      
        }
        else {
          landmark_count++;
          rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [tag_name, le.role]);
        }  
      }  
    }

    if (page_element) {
      // Test if no navigation landmarks
      if (landmark_count === 0) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', []);
      else if (landmark_count === 1) rule_result.addResult(TEST_RESULT.PASS, page_element, 'PASS_1', []);
      else rule_result.addResult(TEST_RESULT.FAIL, page_element, 'ACTION_1', []);
    } 
    
  } // end validate function
},

/**
 * @object LANDMARK_6
 *
 * @desc Headings should be properly nested in a landmark
 */               
{ rule_id             : 'LANDMARK_6', 
  last_updated        : '2012-07-14', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '1.3.1',
  wcag_related_ids    : ['2.4.1', '2.4.6', '2.4.10'],
  target_resources    : ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties : ['tag_name', 'name', 'parent_landmark'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     function getNextVisibleHeadingWithContent(flag) {
     
       if (typeof flag !== 'boolean' || flag) heading_index++;
     
       while (heading_index < heading_elements_len) {
       
         var he = heading_elements[heading_index];
         var de = he.dom_element;
         var vf = de.computed_style.is_visible_to_at;
         
         if ((vf === VISIBILITY.VISIBLE) && 
             (he.name.length > 0)) {
           return he;
         }
         
         if ((vf === VISIBILITY.VISIBLE) && 
             (he.name.length === 0)) {
           rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_3', [de.tag_name, le]);          
         }
         
         heading_index++;          
         
       }
       
       return null;
     }

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var SOURCE      = OpenAjax.a11y.SOURCE;
  
    var landmark_elements     = dom_cache.headings_landmarks_cache.landmark_elements;
    var landmark_elements_len = landmark_elements.length;
    
    var tag_name = "";

    for (var i = 0; i < landmark_elements_len; i++ ) {
      var le = landmark_elements[i];
      var cs = le.dom_element.computed_style;

      if (cs.is_visible_to_at) {

        var heading_elements     = le.getHeadings();
        var heading_elements_len = heading_elements.length;
        var heading_index = 0;
        var heading_fail  = 0;
                
//        OpenAjax.a11y.logger.debug("  Landmark: " + le  +  " Number of Headings: " + heading_elements_len);

        var page_element  = dom_cache.headings_landmarks_cache.page_element;

        var he_last = getNextVisibleHeadingWithContent(false);

        var fail_count = 0;
    
        if (he_last && page_element) {

          var de = he_last.dom_element;
      
          var he = getNextVisibleHeadingWithContent();
      
          while (he) {
      
            de = he.dom_element;
        
            if (he.level > (he_last.level + 1) ) {
              rule_result.addResult(TEST_RESULT.FAIL, he, 'ACTION_2', [de.tag_name, le.role]); 
              fail_count++;
            }
        
            he_last = he;
            he = getNextVisibleHeadingWithContent();
          }
        }  
        
        if (fail_count > 0) rule_result.addResult(TEST_RESULT.FAIL, le, 'ACTION_1', [fail_count, heading_elements_len, le.role]);
        else if (heading_elements_len > 1) rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [heading_elements_len, le.role]);
        
      }
      else {
        rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [le.role, fail_count, heading_elements_len]);      
      }
      
    }
    
  } // end validate function
}


]); 


    

/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*      OpenAjax Alliance Table Rules                               */
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/** 

   **
 * @object LAYOUT_1
 *
 * @desc     Make sure content is in a meaningful sequence
 *           tables used for layout must be checked for 
 *           maintaining meanful sequence
 */
 { rule_id             : 'LAYOUT_1', 
   last_updated        : '2012-12-28', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER,
   wcag_primary_id     : '1.3.2',
   wcag_related_ids    : ['1.3.1'],
 
   target_resources    : ['table'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['is_data_table', 'max_column', 'max_row', 'nesting_level'],
   language_dependency : "",
   validate            : function (dom_cache, rule_result) {
     
     function getNestingLevel(table_element, level) {
     
       var l = level;
       var pte = table_element.parent_table_element;
     
       if (pte) {
         if (pte.is_data_table || pte.max_column == 1) {
           l = getNestingLevel(pte, level);           
         }
         else {
           l = getNestingLevel(pte, (level+1));
         }
       }
       return l;
     }
     
     var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY    = OpenAjax.a11y.VISIBILITY;
    
     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     
     var page_element = dom_cache.headings_landmarks_cache.page_element;
     rule_result.addResult(TEST_RESULT.MANUAL_CHECK, page_element, 'MANUAL_CHECK_1', []); 

     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (var i = 0; i < table_elements_len; i++) {
       
         var te = table_elements[i];
         var de = te.dom_element;
         var cs = de.computed_style;
         
         if (!te.is_data_table) {

           if (cs.is_visible_to_at === VISIBILITY.VISIBLE) {
         
             var nesting_level = getNestingLevel(te, 0);
             
             te.nesting_level = nesting_level;

             if (te.max_column === 1)  {
               rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_1', []);          
             }  
             else {
         
               if (nesting_level === 0) {
                 rule_result.addResult(TEST_RESULT.MANUAL_CHECK, te, 'MANUAL_CHECK_2', [te.max_row, te.max_column]);               
               } 
               else {
                 rule_result.addResult(TEST_RESULT.FAIL, te, 'MANUAL_CHECK_3', [te.nesting_level]);
               }  
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', []);
           }
         } 
       } // end loop
     }  
     
   }  // end validation function
 },
 
/**
 * @object LAYOUT_2
 *
 * @desc     Do not use nested tables more than 1 column wide for positioning content 
 *           Fails with one or more one levels of nesting.
 */
 { rule_id             : 'LAYOUT_2', 
   last_updated        : '2012-12-28', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER,
   wcag_primary_id     : '1.3.2',
   wcag_related_ids    : [],
 
   target_resources    : ['table'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['is_data_table', 'max_column', 'max_row', 'nesting_level'],
   language_dependency : "",
   validate          : function (dom_cache, rule_result) {
   
     var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY    = OpenAjax.a11y.VISIBILITY;
    
     var i;
     var te;
     var nesting_level;
    
     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     

     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (i=0; i < table_elements_len; i++) {
       
         te = table_elements[i];
         
         if (!te.is_data_table) {
                      
           if (te.dom_element.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
         
             if (te.max_column > 1) {
             
               if (te.nesting_level > 0) rule_result.addResult(TEST_RESULT.FAIL, te, 'ACTION_1', [te.max_row, te.max_column, te.nesting_level]);
               else rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_1', []);                       
             }  
             else {
               rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_2', []);          
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', []);
           } 
         }  
       } // end loop
     }  
  } // end validation function        
},
 
 /**
 * @object LAYOUT_3
 *
 * @desc  If table is used for layout, the rule tests if the table element and any of its child table 
 *        related elements (i.e. tbody, tr, td) have a role attribute with the value 'presentation' (role="presentation")
 */
 { rule_id             : 'LAYOUT_3', 
   last_updated        : '2012-12-28', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STYLE_READING_ORDER,
   wcag_primary_id     : '1.3.2',
   wcag_related_ids    : ['1.3.1', '4.1.2'],
 
   target_resources    : ['table'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['role'],
   language_dependency : "",
   validate          : function (dom_cache, rule_result) {
   
     function checkLayoutTableForRolePresentation(element) {
     
       var de = element.dom_element;
       
       if (de.role && de.role == 'presentation') {
         rule_result.addResult(TEST_RESULT.PASS, element, 'PASS_1', [de.tag_name]);       
       }
       else {
         rule_result.addResult(TEST_RESULT.FAIL, element, 'ACTION_1', [de.tag_name]);
       }

       var cce     = element.child_cache_elements;
       
       if (!cce) return;
       
       var cce_len = cce.length;     
       
       if (!cce_len) return;
       
       for (var j = 0; j < cce_len; j++) {
         // do not recursively go into other tables
         if (cce[j].table_type && (cce[j].table_type !== TABLE.TABLE_ELEMENT)) checkLayoutTableForRolePresentation(cce[j]);
       }
     
     }
   
     var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY    = OpenAjax.a11y.VISIBILITY;    
     var TABLE      = OpenAjax.a11y.TABLE;
    
     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     
     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (var i = 0; i < table_elements_len; i++) {
       
         var te = table_elements[i];
         var de = te.dom_element;         
         
         if (!te.is_data_table) {
           if (te.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {
             checkLayoutTableForRolePresentation(te);
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', [de.tag_name]);
           }
         }  
       } // end loop
     }  
   } // end validation function
 }  
]); /**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*            OpenAjax Alliance Link Rules                         */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/*
 * @object LINK_1
 *
 * @desc Links with the same HREF should have the same link text.
 */
     
{ rule_id             : 'LINK_1', 
  last_updated        : '2012-09-22', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.LINKS,
  wcag_primary_id     : '2.4.4',
  wcag_related_ids    : ['2.4.9'],
  target_resources    : ['a', 'area', '[role=link]'],
  cache_dependency    : 'links_cache',
  resource_properties : ['href', 'accessible_name', 'accessible_name_source'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
    function updateResults(links, test_result, message) {
    
      for (var i = 0; i < links.length; i++) {
      
        var le = links[i];
        var links_len = links[i].length;
      
//        OpenAjax.a11y.logger.debug("  Update Item: " + i + " of " + end + " le: " + le.toString());

        var tag_name  = le.dom_element.tag_name;
              
        rule_result.addResult(test_result, le,  message, [tag_name, links_len]);        
      }
      
    }

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    
    var same_hrefs     = dom_cache.links_cache.getLinksThatShareTheSameHREF();
    var same_hrefs_len = same_hrefs.length;

    for (var i = 0; i < same_hrefs_len; i++) {
      
      var same_href = same_hrefs[i];
      
      if (same_href.same_names) updateResults(same_href.links, TEST_RESULT.PASS, 'PASS_1');
      else if (same_href.names_count === 2) updateResults(same_href.links, TEST_RESULT.MANUAL_CHECK, 'MANUAL_CHECK_1');
      else updateResults(same_href.links, TEST_RESULT.FAIL, 'ACTION_1');
                  
    }  // end loop  
    
  } // end validate function
 },

/**
 * @object LINK_2
 *
 * @desc Links with the different HREFs should have the unique accessible names
 */ 
     
{ rule_id             : 'LINK_2', 
  last_updated        : '2012-09-22', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.LINKS,
  wcag_primary_id     : '2.4.4',
  wcag_related_ids    : ['2.4.9'],

  target_resources    : ['a', 'area', '[role=link]'],
  cache_dependency    : 'links_cache',
  resource_properties : ['href', 'accessible_description', 'accessible_name', 'accessible_name_source'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    function updateResults(links, test_result, message) {
    
      for (var i = 0; i < links.length; i++) {
      
        var le = links[i];
        var links_len = links[i].length;
      
//        OpenAjax.a11y.logger.debug("  Update Item: " + i + " of " + end + " le: " + le.toString());

        var tag_name  = le.dom_element.tag_name;
              
        rule_result.addResult(test_result, le,  message, [tag_name, links_len]);        
      }
      
    }

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    
    var same_names     = dom_cache.links_cache.getLinksThatShareTheSameName();
    var same_names_len = same_names.length;

    for (var i = 0; i < same_names_len; i++) {
      
      var same_name = same_names[i];
      
      if (same_name.same_hrefs) updateResults(same_name.links, TEST_RESULT.PASS, 'PASS_1');
      else updateResults(same_name.links, TEST_RESULT.FAIL, 'ACTION_1');
                  
    }  // end loop  

  } // end validate function
 },
 
/**
 * @object LINK_3 
 * 
 * @desc Links should have minimum dimensions for selecting and reading
 */
 
{ rule_id             : 'LINK_3', 
  last_updated        : '2012-09-22', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.LINKS,
  wcag_primary_id     : '2.4.4',
  wcag_related_ids    : ['2.4.9'],

  target_resources    : ['a', 'area', '[role=link]'],
  cache_dependency    : 'links_cache',
  resource_properties    : ['height', 'width', 'is_visible_onscreen'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    
    var MIN_HEIGHT = 12;
    var MIN_WIDTH = 12;

    var passed = true;
    var node_result = null;
   
    // Check to see if valid cache reference
    if (dom_cache.links_cache.link_elements) {
     
      var link_elements_len = dom_cache.links_cache.link_elements.length;
    
      for (var i=0; i < link_elements_len; i++) {
       
        var le = dom_cache.links_cache.link_elements[i];
        var cs = le.dom_element.computed_style;

        var tag_name = le.dom_element.tag_name;

        // test if link is visible in a graphical rendering

        if (le.is_link) {       

          if ((cs.is_visible_onscreen == VISIBILITY.VISIBLE) &
              (cs.height > 0) &&
              (cs.width > 0)) {
   
            if ((cs.height >= MIN_HEIGHT) && 
                 (cs.width >= MIN_WIDTH)) {
              rule_result.addResult(TEST_RESULT.PASS, le, 'PASS_1', [tag_name]);
            }
            else {
              rule_result.addResult(TEST_RESULT.FAIL, le, 'ACTION_1', [tag_name, cs.height, cs.width]);
            }
          }  
          else {
            rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [tag_name]);
          } 
        } // endif
      } // end loop
    } 
  } // end validation function
},

/**
 * @object LINK_4 
 * 
 * @desc Link should describe the target of a link
 */
 
{ rule_id             : 'LINK_4', 
  last_updated        : '2012-09-22', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.LINKS,
  wcag_primary_id     : '2.4.4',
  wcag_related_ids    : ['2.4.9'],

  target_resources    : ['a', 'area', '[role=link]'],
  cache_dependency    : 'links_cache',
  resource_properties : ['accessible_name', 'accessible_name_source', 'href', 'accessible_description'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    
    var link_elements     = dom_cache.links_cache.link_elements;
    var link_elements_len = link_elements.length;

    var visible_link_elements = [];
    
    for (var i = 0; i < link_elements_len; i++) {
      
      var le = link_elements[i];
      var tag_name = le.dom_element.tag_name;
      
      if (le.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE && 
          le.is_link) {
        visible_link_elements.push(le);
      }
      else {
        rule_result.addResult(TEST_RESULT.HIDDEN, le, 'HIDDEN_1', [tag_name]);                  
      }
      
    }
    
    var visible_link_elements_len = visible_link_elements.length;

    for (i = 0; i < visible_link_elements_len; i++) {

      le = visible_link_elements[i];
      
      var name        = le.accessible_name_for_comparison;
      var description = le.accessible_description_for_comparison;
      tag_name        = le.dom_element.tag_name;

      if (name.length) {
        if (description.length) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, le, 'MANUAL_CHECK_2', [tag_name, name, description]);
        else rule_result.addResult(TEST_RESULT.MANUAL_CHECK, le, 'MANUAL_CHECK_1', [tag_name, name]);
      }  
      else {
        rule_result.addResult(TEST_RESULT.FAIL, le, 'ACTION_1', [tag_name]);
      }
      
    }  // end loop  
    
    
  } // end valifdation function
}

      
]); 


    

/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*      OpenAjax Alliance Table Rules                               */
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/** 
 * @object TABLE_1
 * 
 * @desc If a table is a data table, if each data cell has headers
 */
 { rule_id             : 'TABLE_1', 
   last_updated        : '2012-09-23', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
   wcag_primary_id     : '1.3.1',
   wcag_related_ids    : ['2.4.6'],
 
   target_resources    : ['td'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['headers', 'header_content', 'header_source'],
   language_dependency : "",
   validate          : function (dom_cache, rule_result) {
   
     function allReadyDone(span_cell) {
     
       var span_cells_len = span_cells.length;
     
       for (var i = 0; i < span_cells_len; i++) {
         if (span_cell === span_cells[i]) return true;
       }
       
       span_cells.push(span_cell);
       return false;
     }
   
     var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
     var HEADER_SOURCE = OpenAjax.a11y.HEADER_SOURCE;
     var VISIBILITY    = OpenAjax.a11y.VISIBILITY;
   
     var span_cells = [];
   
     var info_row;
     var info_column;
      
     var table_elements   = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     
//     OpenAjax.a11y.logger.debug("=== Table Rule 1 ===");

//     OpenAjax.a11y.logger.debug(" Table Elements on page: " + table_elements_len);
     
     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (var i=0; i < table_elements_len; i++) {
         var te = table_elements[i];
         var is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;

//         OpenAjax.a11y.logger.debug(" Table Element: " + te + "   is data table: " + te.is_data_table);

         if (te.is_data_table) {
     
           var max_row    = te.max_row;
           var max_column = te.max_column;
           var cells      = te.cells;

           for (var r = 0; r < max_row; r++) {
             for (var c = 0; c < max_column; c++) {
           
               var cell = cells[r][c];
             
               if (cell && cell.table_type  === OpenAjax.a11y.TABLE.TD_ELEMENT) {
                 
                 if (is_visible_to_at == VISIBILITY.VISIBLE) {
           
                   if(cell.has_spans && allReadyDone(cell)) continue;
                 
                   if(te.is_complex_data_table) {
                     if (cell.header_source === HEADER_SOURCE.HEADERS_ATTRIBUTE) rule_result.addResult(TEST_RESULT.PASS, cell, 'PASS_1', [cell.headers]);
                     else if (cell.text_content_for_comparison.length === 0) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, cell, 'MANUAL_CHECK_1', []);
                       else rule_result.addResult(TEST_RESULT.FAIL, cell, 'ACTION_1', []);
                   }
                   else {
                     if (cell.header_source === HEADER_SOURCE.HEADERS_ATTRIBUTE) rule_result.addResult(TEST_RESULT.PASS, cell, 'PASS_1', [cell.headers]);
                     else if (cell.header_source === HEADER_SOURCE.ROW_OR_COLUMN_HEADERS) rule_result.addResult(TEST_RESULT.PASS, cell, 'PASS_2', []);
                       else if (cell.text_content_for_comparison.length === 0) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, cell, 'MANUAL_CHECK_1', []);
                         else rule_result.addResult(TEST_RESULT.FAIL, cell, 'ACTION_2', []);
                   }
                 }
                 else {
                  rule_result.addResult(TEST_RESULT.HIDDEN, cell, 'HIDDEN_1', []);     
                 }
               }
             }             
           }
         }   
       } // end loop
     }   
   } // end validation function
 },
 
/** 
 * @object TABLE_2T 
 *
 * @desc Tests if a table has either an effective caption or an effective summary with content.
 */
 { rule_id             : 'TABLE_2T', 
   last_updated        : '2012-09-23', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
   wcag_primary_id     : '1.3.2',
   wcag_related_ids    : ['4.1.2'],
 
   target_resources    : ['caption', 'table[sumary]'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['is_data_table', 'effective_caption', 'effective_summary'],
   language_dependency : "",
   validate            : function (dom_cache, rule_result) {
   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;

     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;

     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
       
       for (var i = 0; i < table_elements_len; i++) {
         var te = table_elements[i];
         var is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;       
       
         if (te.is_data_table) {
       
           if (is_visible_to_at == OpenAjax.a11y.VISIBILITY.VISIBLE) {
       
             if ((te.effective_caption && te.effective_caption.length > 0) ||
                 (te.effective_summary && te.effective_summary.length > 0)) {
               if (te.effective_caption && te.effective_caption.length > 0) {    
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_1', [te.effective_caption]);     
               }
               else {
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_2', [te.effective_summary]);                    
               }
             }
             else {
               rule_result.addResult(TEST_RESULT.FAIL, te, 'ACTION_1', []);
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', []);     
           } 
         }
       } // end loop
     } 
   } // end validation function
 },
 
/** 
 * @object TABLE_2S 
 *
 * @desc  If there is only one data table on a page, the rule tests the table for an effective caption.
 */
 { rule_id             : 'TABLE_2S', 
   last_updated        : '2012-09-23', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
   wcag_primary_id     : '1.3.1',
   wcag_related_ids    : ['2.4.6'],
 
   target_resources    : ['caption', 'table[sumary]'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['is_data_table', 'effective_caption', 'effective_summary'],
   language_dependency : "",
   enabled           : true,
   validate          : function (dom_cache, rule_result) {
   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
     
     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;

     var data_table_count = 0;

     // count the number of data tables
     for (var i = 0; i < table_elements_len; i++) {
       var te = table_elements[i];
       var is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;       
       
       if (te.is_data_table && is_visible_to_at === VISIBILITY.VISIBLE) data_table_count++;
     }

     if (data_table_count !== 1) return;

     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
       
       for (i = 0; i < table_elements_len; i++) {
         te = table_elements[i];
         is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;       
       
         if (te.is_data_table) {
       
           if (is_visible_to_at == OpenAjax.a11y.VISIBILITY.VISIBLE) {
       
             if ((te.effective_caption && te.effective_caption.length > 0) ||
                 (te.effective_summary && te.effective_summary.length > 0)) {
                 
               if (te.effective_caption && te.effective_caption.length > 0) {    
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_1', [te.effective_caption]);     
               }
               else {
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_2', [te.effective_summary]);                    
               }
             }  
             else {
               rule_result.addResult(TEST_RESULT.FAIL, te, 'ACTION_1', []);
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', []);     
           } 
         }
       } // end loop
     } 
   } // end validation function
 },
 
/** 
 * @object TABLE_2M 
 *
 * @desc  If there is more than one data table on a page, the rule tests the table for an effective caption.
 */
 { rule_id             : 'TABLE_2M', 
   last_updated        : '2012-09-23', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
   wcag_primary_id     : '1.3.1',
   wcag_related_ids    : ['2.4.6'],
 
   target_resources    : ['caption', 'table[sumary]'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['is_data_table', 'effective_caption', 'effective_summary'],
   language_dependency : "",
   validate          : function (dom_cache, rule_result) {
   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
     
     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;

     var data_table_count = 0;

     // count the number of data tables
     for (var i = 0; i < table_elements_len; i++) {
       var te = table_elements[i];
       var is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;       
       if (te.is_data_table && is_visible_to_at === VISIBILITY.VISIBLE) data_table_count++;
     }

     if (data_table_count <= 1) return;

     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
       
       for (i = 0; i < table_elements_len; i++) {
         te = table_elements[i];
         is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;       
       
         if (te.is_data_table) {
       
           if (is_visible_to_at == OpenAjax.a11y.VISIBILITY.VISIBLE) {
       
             if ((te.effective_caption && te.effective_caption.length > 0) ||
                 (te.effective_summary && te.effective_summary.length > 0)) {
                 
               if (te.effective_caption && te.effective_caption.length > 0) {    
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_1', [te.effective_caption]);     
               }
               else {
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_2', [te.effective_summary]);                    
               }
             }  
             else {
               rule_result.addResult(TEST_RESULT.FAIL, te, 'ACTION_1', []);
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', []);     
           } 
         }
       } // end loop
     } 
   } // end validation function
 },
 
/**
 * @object TABLE_3
 *
 * @desc  Tests when a table has both and effective caption and effective summary that the two are unique 
 */
 
 { rule_id             : 'TABLE_3', 
   last_updated        : '2012-09-23', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
   wcag_primary_id     : '1.3.1',
   wcag_related_ids    : ['2.4.6'],
 
   target_resources    : ['caption', 'table[sumary]'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['is_data_table', 'effective_caption', 'effective_summary'],
   language_dependency : "",
   validate          : function (dom_cache, rule_result) {
   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
     
     var table_elements     = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;

     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (var i = 0; i < table_elements_len; i++) {
         var te = table_elements[i];
         var es = te.effective_summary_for_comparison;
         var ec = te.effective_caption_for_comparison;
         var is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;
      
         if (te.is_data_table) {
         
           if (is_visible_to_at == VISIBILITY.VISIBLE) {
           
             OpenAjax.a11y.logger.debug(" Effective Summary: " + es + "  Effective Caption: " + ec);
           
           
             if ((es && es.length > 0) && 
                 (ec && ec.length > 0)) {
              
               if (es !== ec) {
                 rule_result.addResult(TEST_RESULT.PASS, te, 'PASS_1', []);     
               }
               else {
                 rule_result.addResult(TEST_RESULT.FAIL, te, 'ACTION_1', []);     
               }
             }  
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, te, 'HIDDEN_1', []);     
           }
         }
       } // end loop
     }
   } // end validation function
 },

/** 
 * @object TABLE_4
 *
 * @desc    Tests if table headers use TH elements instead of TD with SCOPE
 */
 
 { rule_id             : 'TABLE_4', 
   last_updated        : '2012-09-23', 
   rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
   rule_category       : OpenAjax.a11y.RULE_CATEGORIES.DATA_TABLES,
   wcag_primary_id     : '1.3.1',
   wcag_related_ids    : ['2.4.6'],
 
   target_resources    : ['caption', 'table[sumary]'],
   cache_dependency    : 'tables_cache',
   resource_properties : ['tag_name', 'scope'],
   language_dependency : "",
   validate          : function (dom_cache, rule_result) {

     function allReadyDone(span_cell) {
     
       var span_cells_len = span_cells.length;
     
       for (var i = 0; i < span_cells_len; i++) {
         if (span_cell === span_cells[i]) return true;
       }
       
       span_cells.push(span_cell);
       return false;
     }
   
     var TEST_RESULT   = OpenAjax.a11y.TEST_RESULT;
     var VISIBILITY    = OpenAjax.a11y.VISIBILITY;
   
     var span_cells = [];
   
     var info_row;
     var info_column;
      
     var table_elements   = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     
     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (var i=0; i < table_elements_len; i++) {
         var te = table_elements[i];
         var is_visible_to_at = te.dom_element.computed_style.is_visible_to_at;

         if (te.is_data_table) {
     
           var max_row    = te.max_row;
           var max_column = te.max_column;
           var cells      = te.cells;

           for (var r = 0; r < max_row; r++) {
             for (var c = 0; c < max_column; c++) {
           
               var cell = cells[r][c];
             
               if (cell && cell.table_type  === OpenAjax.a11y.TABLE.TH_ELEMENT) {
                 
                 if (is_visible_to_at == VISIBILITY.VISIBLE) {
           
                   if(cell.has_spans && allReadyDone(cell)) continue;
                 
                   if(cell.dom_element.tag_name === 'th') rule_result.addResult(TEST_RESULT.PASS, cell, 'PASS_1', []);
                   else rule_result.addResult(TEST_RESULT.FAIL, cell, 'ACTION_1', []);
                   
                 }
                 else {
                  rule_result.addResult(TEST_RESULT.HIDDEN, cell, 'HIDDEN_1', []);     
                 }
               }
             }             
           }
         }   
       } // end loop
     }   
   } // end validation function  
}

/**
 * @object TABLE_5
 *
 * @desc   The rule tests the table for an effective summary.
 *
 { id                : 'TABLE_5', 
   last_updated      : '2011-12-14', 
   cache_dependency  : 'tables_cache',
   resource_properties : ['effective_summary'],
   language          : "",
   validate          : function (dom_cache, rule_result) {
   
     var SEVERITY = OpenAjax.a11y.SEVERITY;
     var VISIBILITY = OpenAjax.a11y.VISIBILITY;
     
     var table_elements = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     
     var i;   
     var te;
     var tc;
   
     var data_table_count = 0;
       
     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (i=0; i < table_elements_len; i++) {
         te = table_elements[i];
       
         if (te.is_data_table) {
       
           if (te.dom_element.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
       
             if (te.effective_summary && te.effective_summary.length > 0) {
               rule_result.addResult(SEVERITY.PASS, te, 'MESSAGE_PASS', [te.effective_summary]);
             } 
             else {
               rule_result.addResult(SEVERITY.FAIL, te, 'MESSAGE_VIOLATION', []);
             }  
           }
           else {
             rule_result.addResult(SEVERITY.HIDDEN, te, 'MESSAGE_HIDDEN', []);
           } 
         }
         else {
           rule_result.addResult(SEVERITY.NA, te, 'MESSAGE_NOT_DATA_TABLE', []);
         } 
       }
     }    
  } // end validation function
},


 **
 * @object TABLE_6
 *
 * @desc    Each TH element with content in a complex table 
 *          must have an id attribute, whose id value must be unique 
 *          on the page
 *
 { id                : 'TABLE_6', 
   last_updated      : '2011-09-17', 
   cache_dependency  : 'tables_cache',
   resource_properties : ['id'],
   language          : "",
   validate          : function (dom_cache, rule_result) {
   
     function checkComplexDataTableHeadingsForUniqueIds(table_element) {

       function testHeaderCellForUniqueId(table_elements) {
         var j;
         var tce;
         var max = table_elements.length;
       
         for (j = 0; j < max; j++) {
           tce = table_elements[j];

           // do not traverse nested tables
           if (tce.table_type === TABLE.TABLE_ELEMENT) checkComplexDataTableHeadingsForUniqueIds(tce);  

           switch (tce.table_type) {
         
           case TABLE.TH_ELEMENT:
         
             total++;
         
             if (!tce.text_content) tce.text_content = tce.dom_element.getText().normalizeSpace();               
           
               if (tce.text_content.length !== 0) {                 
                 
                 if (tce.dom_element.id_unique === ID.NOT_UNIQUE) {
                   rule_result.addResult(SEVERITY.FAIL, tce, 'MESSAGE_NOT_UNIQUE_VIOLATION', [tce.dom_element.id]);              
                   count++;
                 }
                 else {
                   if (tce.dom_element.id_unique === ID.NOT_DEFINED) {
                     rule_result.addResult(SEVERITY.FAIL, tce, 'MESSAGE_NO_ID_VIOLATION', []);              
                     count++;
                   }
                   else {
                     rule_result.addResult(SEVERITY.PASS, tce, 'MESSAGE_PASS', []);    
                   }                     
                 }
               }
               else {
                 rule_result.addResult(SEVERITY.MANUAL_CHECK, tce, 'MESSAGE_NO_CONTENT', []);    
               }
             break;

           case TABLE.TD_ELEMENT:
             rule_result.addResult(SEVERITY.NA, tce, 'MESSAGE_DATA_CELL', []);                                        
             break;
           
           default:
             break;

           } // end switch  
         
           if (tce.child_cache_elements && tce.child_cache_elements.length) testHeaderCellForUniqueId(tce.child_cache_elements);         
       
         } // end loop
     
       } // end function


       var count = 0;
       var total = 0;

       if (table_element.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {

         if (table_element.is_complex_data_table) { 

           testHeaderCellForUniqueId(table_element.child_cache_elements);

           if (count === 0) {      
             rule_result.addResult(SEVERITY.INFORMATIONAL, table_element, 'MESSAGE_TABLE_PASS', [total]);                
           }
           else {
             rule_result.addResult(SEVERITY.INFORMATIONAL, table_element, 'MESSAGE_TABLE_PROBLEM_IDS', [count, total]);                
           }
         }
         else {
           if (table_element.is_data_table) {
             rule_result.addResult(SEVERITY.NA, table_element, 'MESSAGE_TABLE_SIMPLE_DATA_TABLE', []);                    
           }
           else {
             rule_result.addResult(SEVERITY.NA, table_element, 'MESSAGE_TABLE_LAYOUT_TABLE', []);                    
           }  
         }
       }
       else {
         rule_result.addResult(SEVERITY.NA, table_element, 'MESSAGE_TABLE_HIDDEN', []);          
       }
     }

     var SEVERITY   = OpenAjax.a11y.SEVERITY;
     var VISIBILITY = OpenAjax.a11y.VISIBILITY;
     var TABLE      = OpenAjax.a11y.TABLE;
     var ID         = OpenAjax.a11y.ID;
     
     var i;   
           
     var child_cache_elements     = dom_cache.tables_cache.child_cache_elements;
     var child_cache_elements_len = child_cache_elements.length;


     
     for (i = 0; i < child_cache_elements_len; i++) {
       checkComplexDataTableHeadingsForUniqueIds(child_cache_elements[i]);     
     } // end loop
     
   } // end validation function
 },

 **
 * @object TABLE_7
 *
 * @desc  If a table is a complex data table, all the TD elements with content must have a headers attribute that point to TH elements in the same table
 *
 { id                : 'TABLE_7', 
   last_updated      : '2011-09-17', 
   cache_dependency  : 'tables_cache',
   resource_properties : ['headers'],
   language          : "",
   validate          : function (dom_cache, rule_result) {

     function checkComplexDataTableForDataCellHeaders(table_element) {

       function testForDataCellHeaders(table_elements) {
         var j;
         var tce;
         var max = table_elements.length;
       
         for (j = 0; j < max; j++) {
           tce = table_elements[j];

           // do not traverse nested tables
           if (tce.table_type === TABLE.TABLE_ELEMENT) checkComplexDataTableForDataCellHeaders(tce);  

           switch (tce.table_type) {
         
           case TABLE.TD_ELEMENT:
         
             if (!tce.text_content) tce.text_content = tce.dom_element.getText().normalizeSpace();               
           
             if (tce.headers && tce.headers_array && tce.headers_array.length > 0) {
               rule_result.addResult(SEVERITY.PASS, tce, 'MESSAGE_HAS_HEADERS', []);                                                    
               total++;       
             }
             else {
               if (tce.text_content.length) {
                 rule_result.addResult(SEVERITY.FAIL, tce, 'MESSAGE_MISSING_VIOLATION', []);              
               
                 total++;     
                 count++;
               }
               else {
                 rule_result.addResult(SEVERITY.MANUAL_CHECK, tce, 'MESSAGE_DATA_CELL_IS_EMPTY', []);                                         
               }
             }
             break;

           case TABLE.TH_ELEMENT:
             rule_result.addResult(SEVERITY.NA, tce, 'MESSAGE_HEADER_CELL', []);                                        
             break;
           
           default:
             break;

           } // end switch  
         
           if (tce.child_cache_elements && tce.child_cache_elements.length) testForDataCellHeaders(tce.child_cache_elements);         
       
         } // end loop
     
       } // end function


       var count = 0;
       var total = 0;

       if (table_element.dom_element.computed_style.is_visible_to_at === VISIBILITY.VISIBLE) {

         if (table_element.is_complex_data_table) { 

           testForDataCellHeaders(table_element.child_cache_elements);

           if (count === 0) {      
             rule_result.addResult(SEVERITY.INFORMATIONAL, table_element, 'MESSAGE_TABLE_PASS', [total]);                
           }
           else {
             rule_result.addResult(SEVERITY.INFORMATIONAL, table_element, 'MESSAGE_TABLE_MISSING_HEADERS', [count, total]);                
           }
         }
         else {
           if (table_element.is_data_table) {
             rule_result.addResult(SEVERITY.NA, table_element, 'MESSAGE_TABLE_SIMPLE_DATA_TABLE', []);                    
           }
           else {
             rule_result.addResult(SEVERITY.NA, table_element, 'MESSAGE_TABLE_LAYOUT_TABLE', []);                    
           }  
         }
       }
       else {
         rule_result.addResult(SEVERITY.NA, table_element, 'MESSAGE_TABLE_HIDDEN', []);          
       }
       
     } // end function

     var SEVERITY   = OpenAjax.a11y.SEVERITY;
     var VISIBILITY = OpenAjax.a11y.VISIBILITY;
     var TABLE      = OpenAjax.a11y.TABLE;
     
     var i;   
           
     var child_cache_elements     = dom_cache.tables_cache.child_cache_elements;
     var child_cache_elements_len = child_cache_elements.length;
       
     for (i = 0; i < child_cache_elements_len; i++) {
       checkComplexDataTableForDataCellHeaders(child_cache_elements[i]);     
     } // end loop
     
   } // end validation function
 },
 
 **
 * @object TABLE_8
 *
 * @desc     If a table is a complex data table, the rule tests if the table has an effective summary
 *
 { id                : 'TABLE_8', 
   last_updated      : '2011-12-14', 
   cache_dependency  : 'tables_cache',
   resource_properties : ['effective_summary'],
   language          : "",
   validate          : function (dom_cache, rule_result) {
   
     var SEVERITY = OpenAjax.a11y.SEVERITY;
     var VISIBILITY = OpenAjax.a11y.VISIBILITY;
     
     var table_elements = dom_cache.tables_cache.table_elements;
     var table_elements_len = table_elements.length;
     
     var i;   
     var te;
     var tc;
   

     
     // Check to see if valid cache reference
     if (table_elements && table_elements_len) {
     
       for (i=0; i < table_elements_len; i++) {
         te = table_elements[i];
       
         if (te.is_complex_data_table) {
       
           if (te.dom_element.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
       
             if (te.effective_summary && te.effective_summary.length > 0) {
               rule_result.addResult(SEVERITY.PASS, te, 'MESSAGE_PASS', [te.effective_summary]);
             } 
             else {
               rule_result.addResult(SEVERITY.FAIL, te, 'MESSAGE_VIOLATION', []);
             }  
           }
           else {
             rule_result.addResult(SEVERITY.HIDDEN, te, 'MESSAGE_HIDDEN', []);
           } 
         }
         else {
           rule_result.addResult(SEVERITY.NA, te, 'MESSAGE_NOT_COMPLEX_DATA_TABLE', []);
         } 
       }
     }    
  } // end validation function
 },
 */
 ]); 


    

/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Heading and Landmark Rules                    */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([
      
/**
 * @object TITLE_1
 *
 * @desc The page must contain exactly one title element and it must contain content.
 */               
 
{ rule_id             : 'TITLE_1', 
  last_updated        : '2012-09-11', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['title'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name', 'name_for_comparison'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
//      OpenAjax.a11y.logger.debug("TITLE 1: has title? " + dom_cache.document_has_title);

      var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
  
      var title_element  = dom_cache.headings_landmarks_cache.title_element;
      
      if (dom_cache.document_has_title) {
      
        if (title_element.name_for_comparison.length) {
          rule_result.addResult(TEST_RESULT.PASS, title_element, 'PASS_1', []);                        
        }
        else {
          rule_result.addResult(TEST_RESULT.FAIL, title_element, 'ACTION_1', []);        
        }
      }
      else {
        rule_result.addResult(TEST_RESULT.FAIL, title_element, 'ACTION_2', []);
      }
        
    } // end validate function
  },
/**
 * @object TITLE_2
 *
 * @desc The page must contain exactly one title element and it must contain content.
 */               
 
{ rule_id             : 'TITLE_2', 
  last_updated        : '2012-09-11', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['title'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name', 'name_for_comparison'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
      var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
  
      var title_element  = dom_cache.headings_landmarks_cache.title_element;
      
      if (dom_cache.document_has_title) {
      
        if (title_element.name_for_comparison.length) {
          rule_result.addResult(TEST_RESULT.MANUAL_CHECK, title_element, 'MANUAL_CHECK_1', []);                        
        }
        else {
          rule_result.addResult(TEST_RESULT.FAIL, title_element, 'ACTION_1', []);        
        }
      }
      else {
        rule_result.addResult(TEST_RESULT.FAIL, title_element, 'ACTION_2', []);
      }
        
    } // end validate function
  },
/**
 * @object TITLE_3
 *
 * @desc The words in the @h1@ content must be part of the title element text content.
 *
 */               
 
{ rule_id             : 'TITLE_3', 
  last_updated        : '2012-09-11', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.PAGE,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.STRUCTURE_NAVIGATION,
  wcag_primary_id     : '2.4.2',
  wcag_related_ids    : ['1.3.1', '2.4.6'],

  target_resources    : ['title'],
  cache_dependency    : 'headings_landmarks_cache',
  resource_properties    : ['tag_name', 'name', 'name_for_comparison'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
      var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
  
      var title_element  = dom_cache.headings_landmarks_cache.title_element;
      
      if (dom_cache.document_has_title) {
      
        if (title_element.name_for_comparison.length) {
          rule_result.addResult(TEST_RESULT.MANUAL_CHECK, title_element, 'MANUAL_CHECK_1', []);                        
        }
        else {
          rule_result.addResult(TEST_RESULT.FAIL, title_element, 'ACTION_1', []);        
        }
      }
      else {
        rule_result.addResult(TEST_RESULT.FAIL, title_element, 'ACTION_2', []);
      }
        
    } // end validate function
  }
 ]); 


    

/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* ---------------------------------------------------------------- */
/*            OpenAjax Alliance Media Rules                         */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([

/**
 * @object VIDEO_1
 *
 * @desc Video only must have equivalents
 */ 
  {
  rule_id             : 'VIDEO_1', 
  last_updated        : '2012-10-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
  wcag_primary_id     : '1.2.1',
  wcag_related_ids    : ['1.2.2', '1.2.4'],

  target_resources    : ['embed', 'object', 'video'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tag_name', 'name', 'type', 'src', 'alt'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var MEDIA       = OpenAjax.a11y.MEDIA;
  
    var media_elements     = dom_cache.media_cache.media_elements;
    var media_elements_len = media_elements.length;

    for (var i = 0; i < media_elements_len; i++ ) {
      var me = media_elements[i];
      var tag_name = me.dom_element.tag_name;
      if (me.dom_element.computed_style.is_visible_onscreen === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, me, 'HIDDEN_1', [tag_name]);                      
      }
      else {
        
        if (me.is_video === MEDIA.YES && me.is_audio === MEDIA.NO) {
        
          if (me.has_audio_description === MEDIA.YES) {
            rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_1', [tag_name]);
          }
          else if (me.has_text_alternative === MEDIA.YES) {
            rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_2', [tag_name]);
          }
          else if (me.has_audio_description === MEDIA.MAYBE || me.has_text_alternative === MEDIA.MAYBE) {
             rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_1', [tag_name]);
          }
          else {
            rule_result.addResult(TEST_RESULT.FAIL, me, 'ACTION_1', [tag_name]);
          }    
        }
        else {
          if (me.is_video === MEDIA.MAYBE ||
              (me.is_video === MEDIA.YES && me.is_audio === MEDIA.MAYBE)) {
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_2', [tag_name]);
          }
        }
      }  
    }
  } // end validate function
 },
 

/**
 * @object VIDEO_2
 *
 * @desc Video with audio must have captions
 */ 
  {
  rule_id             : 'VIDEO_2', 
  last_updated        : '2012-10-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
  wcag_primary_id     : '1.2.2',
  wcag_related_ids    : ['1.2.1', '1.2.4'],

  target_resources    : ['embed', 'object', 'video'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tag_name', 'name', 'type', 'src', 'alt'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var MEDIA       = OpenAjax.a11y.MEDIA;
  
    var media_elements     = dom_cache.media_cache.media_elements;
    var media_elements_len = media_elements.length;

    for (var i = 0; i < media_elements_len; i++ ) {
      var me = media_elements[i];
      var tag_name = me.dom_element.tag_name;
      if (me.dom_element.computed_style.is_visible_onscreen === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, me, 'HIDDEN_1', [tag_name]);                      
      }
      else {
        
        if (me.is_audio === MEDIA.YES && me.is_live === MEDIA.NO) {
        
          switch (me.has_caption) {
            case MEDIA.YES:
              rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_1', [tag_name]);
              break;
              
            case MEDIA.MAYBE:
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_1', [tag_name]);
              break;
              
            default:  
              rule_result.addResult(TEST_RESULT.FAIL, me, 'ACTION_1', [tag_name]);
              break;
          }    
        }
        else {
          if (me.is_video === MEDIA.MAYBE || 
              (me.is_video === MEDIA.YES && me.is_live === MEDIA.MAYBE)) {
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_2', [tag_name]);
          }
        }
      }  
    }

  } // end validate function
 },
 
/**
 * @object VIDEO_3
 *
 * @desc Prerecorded video must have audio description or text equivalent
 */ 
  {
  rule_id             : 'VIDEO_3', 
  last_updated        : '2012-10-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
  wcag_primary_id     : '1.2.3',
  wcag_related_ids    : ['1.2.1', '1.2.5'],

  target_resources    : ['embed', 'object', 'video'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tag_name', 'name', 'type', 'src', 'alt'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {


    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var MEDIA       = OpenAjax.a11y.MEDIA;
  
    var media_elements     = dom_cache.media_cache.media_elements;
    var media_elements_len = media_elements.length;

    for (var i = 0; i < media_elements_len; i++ ) {
      var me = media_elements[i];
      var tag_name = me.dom_element.tag_name;
      if (me.dom_element.computed_style.is_visible_onscreen === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, me, 'HIDDEN_1', [tag_name]);                      
      }
      else {
        
        if (me.is_video === MEDIA.YES && me.is_live === MEDIA.NO) {
        
          switch (me.has_audio_description) {
            case MEDIA.YES:
              rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_1', [tag_name]);
              break;
              
            case MEDIA.MAYBE:
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_1', [tag_name]);
              break;
              
            default:  
              rule_result.addResult(TEST_RESULT.FAIL, me, 'ACTION_1', [tag_name]);
              break;
          }    
        }
        else {
          if (me.is_video === MEDIA.MAYBE || 
              (me.is_video === MEDIA.YES && me.is_live === MEDIA.MAYBE)) {
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_2', [tag_name]);
          }
        }
      }  
    }

  } // end validate function
 },

/**
 * @object VIDEO_4
 *
 * @desc Live video or audio must have captions
 */ 
  {
  rule_id             : 'VIDEO_4', 
  last_updated        : '2012-10-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
  wcag_primary_id     : '1.2.4',
  wcag_related_ids    : ['1.2.2'],

  target_resources    : ['embed', 'object', 'video'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tag_name', 'name', 'type', 'src', 'alt'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {


    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var MEDIA       = OpenAjax.a11y.MEDIA;
  
    var media_elements     = dom_cache.media_cache.media_elements;
    var media_elements_len = media_elements.length;

    for (var i = 0; i < media_elements_len; i++ ) {
      var me = media_elements[i];
      var tag_name = me.dom_element.tag_name;
      if (me.dom_element.computed_style.is_visible_onscreen === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, me, 'HIDDEN_1', [tag_name]);                      
      }
      else {
        
        if (me.is_video === MEDIA.YES && me.is_live === MEDIA.YES) {
        
          switch (me.has_audio_description) {
            case MEDIA.YES:
              rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_1', [tag_name]);
              break;
              
            case MEDIA.MAYBE:
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_1', [tag_name]);
              break;
              
            default:  
              rule_result.addResult(TEST_RESULT.FAIL, me, 'ACTION_1', [tag_name]);
              break;
          }    
        }
        else {
          if (me.is_video === MEDIA.MAYBE || 
              (me.is_video === MEDIA.YES && me.is_live === MEDIA.MAYBE)) {
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_2', [tag_name]);
          }
        }
      }  
    }

  } // end validate function
 },
 
/**
 * @object VIDEO_5
 *
 * @desc Prerecorded video must have audio descriptions
 */ 
  {
  rule_id             : 'VIDEO_5', 
  last_updated        : '2012-10-31', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.AUDIO_VIDEO,
  wcag_primary_id     : '1.2.5',
  wcag_related_ids    : ['1.2.1', '1.2.3'],

  target_resources    : ['embed', 'object', 'video'],
  cache_dependency    : 'media_cache',
  resource_properties : ['tag_name', 'name', 'type', 'src', 'alt'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {


    var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
    var VISIBILITY  = OpenAjax.a11y.VISIBILITY;
    var MEDIA       = OpenAjax.a11y.MEDIA;
  
    var media_elements     = dom_cache.media_cache.media_elements;
    var media_elements_len = media_elements.length;

    for (var i = 0; i < media_elements_len; i++ ) {
      var me = media_elements[i];
      var tag_name = me.dom_element.tag_name;
      if (me.dom_element.computed_style.is_visible_onscreen === VISIBILITY.HIDDEN) {
        rule_result.addResult(TEST_RESULT.HIDDEN, me, 'HIDDEN_1', [tag_name]);                      
      }
      else {
        
        if (me.is_video === MEDIA.YES && me.is_live === MEDIA.NO) {
        
          switch (me.has_audio_description) {
            case MEDIA.YES:
              rule_result.addResult(TEST_RESULT.PASS, me, 'PASS_1', [tag_name]);
              break;
              
            case MEDIA.MAYBE:
              rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_1', [tag_name]);
              break;
              
            default:  
              rule_result.addResult(TEST_RESULT.FAIL, me, 'ACTION_1', [tag_name]);
              break;
          }    
        }
        else {
          if (me.is_video === MEDIA.MAYBE || 
              (me.is_video === MEDIA.YES && me.is_live === MEDIA.MAYBE)) {
            rule_result.addResult(TEST_RESULT.MANUAL_CHECK, me, 'MANUAL_CHECK_2', [tag_name]);
          }
        }
      }  
    }

  } // end validate function
 }
 
]);
/* ---------------------------------------------------------------- */
/*  OpenAjax Alliance Control Rules                                 */ 
/* ---------------------------------------------------------------- */

OpenAjax.a11y.all_rules.addRulesFromJSON([

/**
 * @object WIDGET_1
 * 
 * @desc ARIA Widgets must have accessible names
 */
     
{ rule_id             : 'WIDGET_1', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[role="widget"]'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['accessible_name', 'accessible_description', 'computed_label_source'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widgets     = dom_cache.controls_cache.widget_elements;
     var widgets_len = widgets.length;
       
     // Check to see if valid cache reference
     if (widgets && widgets_len) {
     
       for (var i = 0; i < widgets_len; i++) {
         var we = widgets[i];
         var de = we.dom_element;
  
         if (de.is_widget) {
         
           if (de.computed_style.is_visible_to_at == VISIBILITY.VISIBLE) {
     
             if (we.computed_label && we.computed_label.length) {
               rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_1', [de.role]);     
             }
             else {
               if (!de.role_info.reqName) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, we, 'MANUAL_CHECK_1', [de.role]);  
               else rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_1', [de.role]);     
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, we, 'HIDDEN_1', [de.role]);     
           }
         }  
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_2
 * 
 * @desc Elements with onClick event handlers event handlers need role 
 */
     
{ rule_id             : 'WIDGET_2', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[onClick]'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['tag_name', 'role', 'has_click', 'is_widget'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
    
     function hasDecendantWidgetRole(dom_element) {
       
       function checkChildren(list) {
       
         if (typeof list !== 'object') return false;
       
         var flag = false;
       
         for (var i = 0; (i < list.length) && !flag; i++) {
           
           var de = list[i];
           
           if (de.type != Node.ELEMENT_NODE) continue;
           
           if (de.is_widget) flag = true;

           if (de.child_dom_elements.length) flag = checkChildren(de.child_dom_elements);
            
         }
         
         return flag;
       
       }
          
       return checkChildren(dom_element.child_dom_elements);     
     }  
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var dom_elements     = dom_cache.element_cache.dom_elements;
     var dom_elements_len = dom_elements.length;
       
     if (dom_elements && dom_elements_len) {
     
       for (var i = 0; i < dom_elements_len; i++) {
         var de = dom_elements[i];
         var style = de.computed_style;
       
        if (de.events.has_click) { 
        
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             if (de.tag_name !== "body" && de.tag_name !== "frame" &&  de.tag_name !== "iframe") {
         
               if (de.is_widget) {
                 if (!isNaN(de.tab_index))  rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [de.tag_name]);     
                 else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [de.tag_name, de.role]);
               }
               else if ("input textarea button select".indexOf(de.tag_name) >= 0) {
                   rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_2', [de.tag_name]);     
                 }
                 else if ("a area".indexOf(de.tag_name) >= 0) {
                   rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_3', [de.tag_name]);
                 }
                 else {
                   if (hasDecendantWidgetRole(de)) rule_result.addResult(TEST_RESULT.MANUAL_CHECK, de, 'MANUAL_CHECK_1', [de.tag_name]);
                   else if (!isNaN(de.tab_index)) rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_2', [de.tag_name]);     
                   else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_3', [de.tag_name]);
               }    
             }
             else {
               rule_result.addResult(TEST_RESULT.MANUAL_CHECK, de, 'MANUAL_CHECK_1', [de.tag_name]);
             }
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [de.tag_name]);     
           }
         }  
         
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_3
 * 
 * @desc Elements with role values must have valid widget or landmark roles 
 */
     
{ rule_id             : 'WIDGET_3', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[onClick]'],
  cache_dependency    : 'controls_cache',
  resource_properties    : ['role'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var elements_with_role     = dom_cache.controls_cache.elements_with_role;
     var elements_with_role_len = elements_with_role.length;
     
     if (elements_with_role && elements_with_role_len) {
     
       for (var i = 0; i < elements_with_role_len; i++) {
         var de = elements_with_role[i];
         var style = de.computed_style;
       
         if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
           if (de.is_widget) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [de.role]);     
           else if (de.is_landmark) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_2', [de.role]);
           else if (de.is_live) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_3', [de.role]);
           else if (de.is_section) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_4', [de.role]);
           else if (de.is_abstract) rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [de.role]);
           else if (de.role.length === 0) rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_2', []);
           else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_3', [de.role]);
         }
         else {
           rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [de.tag_name, de.role]);     
         }         
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_4
 * 
 * @desc Elements with ARIA attributes have valid values
 */
     
{ rule_id             : 'WIDGET_4', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[aria-atomic]', 
                         '[aria-autocomplete]', 
                         '[aria-busy]', 
                         '[aria-checked]', 
                         '[aria-controls]', 
                         '[aria-describedby]', 
                         '[aria-disabled]', 
                         '[aria-dropeffect]', 
                         '[aria-expanded]', 
                         '[aria-flowto]', 
                         '[aria-grabbed]', 
                         '[aria-haspopup]', 
                         '[aria-hidden]', 
                         '[aria-invalid]', 
                         '[aria-label]', 
                         '[aria-labelledby]', 
                         '[aria-level]', 
                         '[aria-live]', 
                         '[aria-multiline]', 
                         '[aria-multiselectable]', 
                         '[aria-orientation]', 
                         '[aria-owns]', 
                         '[aria-pressed]', 
                         '[aria-readonly]', 
                         '[aria-relevant]',
                         '[aria-required]',
                         '[aria-selected]',
                         '[aria-sort]',
                         '[aria-valuemax]',
                         '[aria-valuemin]',
                         '[aria-valuenow]',
                         '[aria-valuetext]'],
  cache_dependency    : 'controls_cache',
  resource_properties : [],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     function makeProp(label, value) {
     
       var p = {};
       
       p.label = label;
       p.value = value;
       p.description = "";
       
       return p;
     
     }
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var elements_with_aria_attributes     = dom_cache.controls_cache.elements_with_aria_attributes;
     var elements_with_aria_attributes_len = elements_with_aria_attributes.length;
     
     if (elements_with_aria_attributes && elements_with_aria_attributes_len) {
     
       for (var i = 0; i < elements_with_aria_attributes_len; i++) {
         var de = elements_with_aria_attributes[i];
         var style = de.computed_style;
         var aria_attrs = de.aria_attributes;
         var aria_attrs_len = aria_attrs.length;         

         for (var j = 0; j < aria_attrs_len; j++) {
         
           var attr = aria_attrs[j];
           
           var prop = makeProp(attr.name, attr.value);
         
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             if (attr.is_value_valid && attr.tokens) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [attr.name, attr.value], [prop]);
             else if (attr.is_value_valid) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_2', [attr.name, attr.value, attr.type], [prop]);
             else if (attr.type === 'nmtoken' || attr.type === 'boolean') rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [attr.name, attr.value, attr.tokens], [prop]);
             else if (attr.type === 'nmtokens') rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_2', [attr.name, attr.value, attr.tokens], [prop]);
             else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_3', [attr.name, attr.value, attr.type], [prop]);
           
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [attr.name, attr.value], [prop]);     
           }
           
         } // end loop 
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_5
 * 
 * @desc Elements with ARIA attributes have valid values
 */
     
{ rule_id             : 'WIDGET_5', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[aria-atomic]', 
                         '[aria-autocomplete]', 
                         '[aria-busy]', 
                         '[aria-checked]', 
                         '[aria-controls]', 
                         '[aria-describedby]', 
                         '[aria-disabled]', 
                         '[aria-dropeffect]', 
                         '[aria-expanded]', 
                         '[aria-flowto]', 
                         '[aria-grabbed]', 
                         '[aria-haspopup]', 
                         '[aria-hidden]', 
                         '[aria-invalid]', 
                         '[aria-label]', 
                         '[aria-labelledby]', 
                         '[aria-level]', 
                         '[aria-live]', 
                         '[aria-multiline]', 
                         '[aria-multiselectable]', 
                         '[aria-orientation]', 
                         '[aria-owns]', 
                         '[aria-pressed]', 
                         '[aria-readonly]', 
                         '[aria-relevant]',
                         '[aria-required]',
                         '[aria-selected]',
                         '[aria-sort]',
                         '[aria-valuemax]',
                         '[aria-valuemin]',
                         '[aria-valuenow]',
                         '[aria-valuetext]'],
  cache_dependency    : 'controls_cache',
  resource_properties : [],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
     function makeProp(label, value) {
     
       var p = {};
       
       p.label = label;
       p.value = value;
       p.description = "";
       
       return p;
     
     }
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var elements_with_aria_attributes     = dom_cache.controls_cache.elements_with_aria_attributes;
     var elements_with_aria_attributes_len = elements_with_aria_attributes.length;
     
     if (elements_with_aria_attributes && elements_with_aria_attributes_len) {
     
       for (var i = 0; i < elements_with_aria_attributes_len; i++) {
         var de = elements_with_aria_attributes[i];
         var style = de.computed_style;
         var aria_attrs = de.aria_attributes;
         var aria_attrs_len = aria_attrs.length;         

         for (var j = 0; j < aria_attrs_len; j++) {
         
           var attr = aria_attrs[j];
           
           var prop = makeProp(attr.name, attr.value);
         
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             if (attr.is_valid_attribute) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [attr.name], [prop]);
             else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [attr.name], [prop]);
           
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [attr.name, attr.value], [prop]);     
           }
           
         } // end loop 
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_6
 * 
 * @desc Widgets must have required properties
 */
     
{ rule_id             : 'WIDGET_6', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[role]'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['role'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     function getRequiredPropertiesAndValues(dom_element, required_props) {
    
       var rps = [];
     
       var attrs     = dom_element.aria_attributes;
       var attrs_len = attrs.length;
     
       for (var i = 0; i < required_props.length; i++) {
       
         var prop = required_props[i];
       
         var flag = false;
       
         for (var j = 0; j <attrs_len; j++) {
           if (prop === attrs[j].name) { 
             flag = true;
             break;
           }
         }
         
         var rp = {};
         rp.label = prop;
         rp.description = "";
         rp.defined = flag;
         
         if (flag) {
           rp.value  = attrs[j].value;
         }
         else {
           rp.value  = "undefined";       
         }
         
         rps.push(rp);
       
       }
       
       return rps;
     
     }

     function getPropsString(props) {
     
       var str = "";
       prop_max = props.length - 1;
       
       for (var i = 0; i < props.length; i++ ) {
         str += props[i];
         if (i !== prop_max) str += ", ";
       }
       
       return str;
     
     }
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;
       
//         OpenAjax.a11y.logger.debug("  RULE WIDGET 6: " + de.role + " ("+ de.role_info + ")");
       
         var required_properties = de.role_info.reqProps;
             
         if (required_properties) {
             
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             var props_string   = getPropsString(required_properties);
             var required_props = getRequiredPropertiesAndValues(de, required_properties);
             
             var flag = true;
             
             for (var j = 0; (j < required_props.length) && flag; j++) flag = flag && required_props[j].defined;
             
             if (flag) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [de.role, props_string], required_props);     
             else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [de.role, props_string], required_props);
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [de.role]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_7
 * 
 * @desc Widgets must have required owned elements
 */
     
{ rule_id             : 'WIDGET_7', 
  last_updated        : '2012-12-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[role]'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['role'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
  
     function getRequiredChildRolesString(required_children) {
     
       var str = "";
       required_children_max = required_children.length - 1;
       
       for (var i = 0; i < required_children.length; i++ ) {
         str += required_children[i];
         if (i !== required_children_max) str += ", ";
       }
       
       return str;
     
     }
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;
       
         var required_child_roles = de.role_info.reqChildren;
             
         if (required_child_roles) {
             
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             var flag = false;
             
             for (var j = 0; (j < required_child_roles.length) && !flag; j++) {
               
               var role = required_child_roles[j];
               
               flag = we.hasChildRole(role);
                              
             }
             
             var required_child_roles_string = getRequiredChildRolesString(required_child_roles);
             
             if (flag) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [de.role, required_child_roles_string]);     
             else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [de.role, required_child_roles_string]);
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [de.role]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_8
 * 
 * @desc Widgets must have required parent roles
 */
     
{ rule_id             : 'WIDGET_8', 
  last_updated        : '2012-04-12', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[role]'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['role'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {
  
  
     function getRequiredRolesString(required_roles) {
     
       var str = "";
       required_roles_max = required_roles.length - 1;
       
       for (var i = 0; i < required_roles.length; i++ ) {
         str += required_roles[i];
         if (i !== required_roles_max) str += ", ";
       }
       
       return str;
     
     }
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;
       
         var required_parent_roles = de.role_info.container;
             
         if (required_parent_roles) {
             
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             var flag = false;
             
             for (var j = 0; (j < required_parent_roles.length) && !flag; j++) {
               
               var role = required_parent_roles[j];
               
               flag = we.hasParentRole(role);
                              
             }
             
             var required_roles_string = getRequiredRolesString(required_parent_roles);
             
             if (flag) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [de.role, role]);     
             else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [required_roles_string, de.role]);
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [de.role]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
},
/**
 * @object WIDGET_9
 * 
 * @desc Widgets cannot be owned by more than one widget
 */
     
{ rule_id             : 'WIDGET_9', 
  last_updated        : '2012-12-04', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],

  target_resources    : ['[aria-owns]', '[aria-owns]'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['is_owned', 'owner_controls'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     function getParentWidgetString(list) {
     
       var str = "";
       list_max = list.length - 1;
       
       for (var i = 0; i < list.length; i++ ) {
         str += list[i].toString();
         if (i !== list_max) str += "; ";
       }
       
       return str;
     
     }
   
     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;

         if (we.is_owned) {
             
           if (style.is_visible_to_at == VISIBILITY.VISIBLE || style.is_visible_onscreen == VISIBILITY.VISIBLE ) {
           
             var parent_string = getParentWidgetString(we.owner_controls);
             
             if (we.owner_controls.length === 1) rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_1', [we.toString(), parent_string]);     
             else if (we.owner_controls.length > 1) rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_1', [parent_string, we.toString()]);
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, we, 'HIDDEN_1', [we.toString()]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_10
 * 
 * @desc Range widgets with ariavaluenow mut be in range of aria-valuemin and aria-valuemax
 */
     
{ rule_id             : 'WIDGET_10', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,

  last_updated        : '2012-12-04', 
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],
  target_resources    : ['[role="slider"]','[role="progress"]','[role="scrollbar"]','[role="spinbutton"]'],
  cache_dependency    : 'controls_cache',
  resource_properties : ['aria-valuemin', 'aria-valuenow', 'aria-valuemax'],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     function getNotNumbersString() {
     
       var str = "";
       
       if (isNaN(min)) str += 'aria-valuemin';
       
       if (isNaN(max)) {
         if (str.length > 0) str += ", ";
         str += 'aria-valuemax';
       }  
       
       if (isNaN(value)) {
         if (str.length > 0) str += ", ";
         str += 'aria-valuenow';
       }  

       return str;
     }

     function getNumberCount() {
     
       var count = 0;
       
       if (!isNaN(min)) count++;
       if (!isNaN(max)) count++;
       if (!isNaN(value)) count++;

       return count;
     }

     function hasMaxMin() {
     
       var count = 0;
       
       if (!isNaN(min)) count++;
       if (!isNaN(max)) count++;
 
       return count === 2;
     }

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var widget_elements     = dom_cache.controls_cache.widget_elements;
     var widget_elements_len = widget_elements.length;
     
     if (widget_elements && widget_elements) {
     
       for (var i = 0; i < widget_elements_len; i++) {
         var we = widget_elements[i];
         var de = we.dom_element;
         var style = de.computed_style;

         if (de.has_range) {
             
           if (style.is_visible_to_at === VISIBILITY.VISIBLE) {
           
             var valuetext = de.node.getAttribute('aria-valuetext');
             var min       = parseInt(de.node.getAttribute('aria-valuemin'), 10);
             var max       = parseInt(de.node.getAttribute('aria-valuemax'), 10);
             var value     = parseInt(de.node.getAttribute('aria-valuenow'), 10);
             var number_count = getNumberCount();
             var has_max_min  = hasMaxMin();

             if (typeof valuetext === 'string' && (valuetext.length > 0)) { 
               rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_1', [we, valuetext]);     
             }  
             else {
               if (number_count === 3 || (de.role === 'progressbar' && has_max_min)) {
                 if (min < max) {
                   if ((min <= value) && (value <= max)) rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_2', [we, value, min, max]);     
                   else if (de.role === 'progressbar' && has_max_min)  rule_result.addResult(TEST_RESULT.PASS, we, 'PASS_3', [min, max]);
                   else rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_1', [value, min, max]);
                 }
                 else {
                   rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_2', [min, max]);
                 }
               }
               else {
               
                  if (de.role === 'progressbar' && !has_max_min) {
                    rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_3', [value, min, max]);
                  }
                  else { 
                    var not_numbers_string = getNotNumbersString();

                   if (number_count === 1) rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_4', [not_numbers_string]);
                   else rule_result.addResult(TEST_RESULT.FAIL, we, 'ACTION_5', [not_numbers_string]);
                 }  
               }
             }    
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, we, 'HIDDEN_1', [we.toString()]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
},

/**
 * @object WIDGET_11
 * 
 * @desc Elements with mouse down, mouse move and mouse up events must have roles
 */
     
{ rule_id             : 'WIDGET_11', 
  rule_scope          : OpenAjax.a11y.RULE_SCOPE.ELEMENT,
  rule_category       : OpenAjax.a11y.RULE_CATEGORIES.WIDGETS_SCRIPTS,

  last_updated        : '2012-12-04', 
  wcag_primary_id     : '4.1.2',
  wcag_related_ids    : ['1.3.1', '3.3.2'],
  target_resources    : ['[onmousedown]', '[onmouseup]', '[onmousemove]'],
  cache_dependency    : 'controls_cache',
  resource_properties : [],
  language_dependency : "",
  validate            : function (dom_cache, rule_result) {

     function getEventString() {
     
       var str = "";
     
       for (var i = 0; i < events_prop_list.length; i++) {
         var prop = events_prop_list[i];
         
         if (str.length) str += ", " + prop.label;
         else str += prop.label;
       }
       
       return str;
     
     }

     function hasUIEvents(dom_element) {
     
        var list = [];
     
        var has_events = dom_element.hasMouseEvents(list);
        has_events = has_events || dom_element.hasClickEvents(list);
        has_events = has_events || dom_element.hasDragEvents(list);
        has_events = has_events || dom_element.hasKeyboardEvents(list);
        
        events_prop_list = list;

//        OpenAjax.a11y.logger.debug( dom_element.toString() + "  Number of Events: " + list.length);

        return has_events;
     }

     var VISIBILITY  = OpenAjax.a11y.VISIBILITY;   
     var TEST_RESULT = OpenAjax.a11y.TEST_RESULT;
      
     var dom_elements_with_events     = dom_cache.controls_cache.elements_with_events;
     var dom_elements_with_events_len = dom_elements_with_events.length;
     
     if (dom_elements_with_events_len) {
     
       for (var i = 0; i < dom_elements_with_events_len; i++) {
         var de = dom_elements_with_events[i];
         
         var style = de.computed_style;
         var events = de.events;

         var events_prop_list = [];         

         if (hasUIEvents(de)) {
         
           var events_str = getEventString();
             
           if (style.is_visible_to_at === VISIBILITY.VISIBLE) {
           
             if (de.is_widget) { 
               rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_1', [de.role, events_str], events_prop_list);     
             }  
             else {
               if (de.is_interactive) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_2', [de.tag_name, events_str], events_prop_list);
               else if (de.containsInteractiveElements()) rule_result.addResult(TEST_RESULT.PASS, de, 'PASS_3', [de.tag_name, events_str], events_prop_list);
               else rule_result.addResult(TEST_RESULT.FAIL, de, 'ACTION_1', [de.tag_name, events_str], events_prop_list);
             }  
           }
           else {
             rule_result.addResult(TEST_RESULT.HIDDEN, de, 'HIDDEN_1', [de.tag_name]);     
           }
         }
       } // end loop
     } 
   } // end validation function   
}

]); 


    

/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* --------------------------------------------------------------------------- */
/* OpenAjax Alliance (OAA) Ruleset for WCAG 2.0 Transitional (Beta)           */
/* --------------------------------------------------------------------------- */

OpenAjax.a11y.all_rulesets.addRuleset('WCAG20', {

  title : {
    'default' : "ARIA Transitional",
    'en-us'   : "ARIA Transitional"
  },   

  description : {
    'default' : "The ARIA transitional ruleset is based on current WCAG 2.0 sufficient techniques, when relavent techniques are available.  Recommendations are based on web accessibility and usability best practices using the features of the HTML and ARIA specifications.",
    'en-us'   : "The ARIA transitional ruleset is based on current WCAG 2.0 sufficient techniques, when relavent techniques are available.  Recommendations are based on web accessibility and usability best practices using the features of the HTML and ARIA specifications."  
  },
  
  author : {
    name : "OpenAjax Accessibility Working Group",
    url  : "http://www.openajax.org/member/wiki/Accessibility"
  }, 
  
 
  ruleset_id    : "ARIA_TRANS",
  version       : "0.7 beta",
  last_updated  : "2013-01-18",

  // Assignement of rules to WCAG 2.0 requirements

  rule_mappings : {
   AUDIO_1 : {
       required : true,
       enabled  : true
     },
   COLOR_1 : {
       required : true,
       enabled  : true
     },
   CONTROL_1 : {
       required : true,
       enabled  : true
     },
   CONTROL_2 : {
       required : true,
       enabled  : true
     },
   CONTROL_3 : {
       required : true,
       enabled  : true
     },
   CONTROL_4 : {
       required : true,
       enabled  : true
     },
   CONTROL_5 : {
       required : true,
       enabled  : true
     },
   CONTROL_6 : {
       required : true,
       enabled  : true
     },
   CONTROL_7 : {
       required : true,
       enabled  : true
     },
   CONTROL_8 : {
       required : true,
       enabled  : true
     },
   CONTROL_10 : {
       required : true,
       enabled  : true
     },
   CONTROL_11 : {
       required : true,
       enabled  : true
     },
   CONTROL_12 : {
       required : true,
       enabled  : true
     },
   FOCUS_1 : {
       required : true,
       enabled  : true
     },
   FOCUS_2 : {
       required : true,
       enabled  : true
     },
   FOCUS_3 : {
       required : true,
       enabled  : true
     },
   FOCUS_4 : {
       required : true,
       enabled  : true
     },
   HEADING_1 : {
       required : false,
       enabled  : true
     },
   HEADING_2 : {
       required : false,
       enabled  : true
     },
   HEADING_3 : {
       required : true,
       enabled  : true
     },
   HEADING_4 : {
       required : true,
       enabled  : true
     },
   HEADING_5 : {
       required : true,
       enabled  : true
     },
   IMAGE_1 : {  
       required : true,
       enabled  : true
     },
   IMAGE_2 : {
       required : true,
       enabled  : true
     },
   IMAGE_3 : {
       required : true,
       enabled  : true
     },
   IMAGE_4_EN : {
       required : false,
       enabled  : true
     },
   IMAGE_5 : {
       required : false,
       enabled  : true
     },
   IMAGE_6 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_1 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_2 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_3 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_4 : {
       required : true,
       enabled  : true
     },
   LANDMARK_1 : {
       required : false,
       enabled  : true
     },
   LANDMARK_2 : {
       required : false,
       enabled  : true
     },
   LANDMARK_6 : {
       required : false,
       enabled  : true
     },
   LAYOUT_1 : {
       required : true,
       enabled  : true
     },
   LAYOUT_2 : {
       required : false,
       enabled  : true
     },
   LAYOUT_3 : {
       required : false,
       enabled  : true
     },
   LINK_1 : {
       required : false,
       enabled  : true
     },
   LINK_2 : {
       required : false,
       enabled  : true
     },
   LINK_3 : {
       required : false,
       enabled  : true
     },
   LINK_4 : {
       required : true,
       enabled  : true
     },
   TABLE_1 : {
       required : true,
       enabled  : true
     },
   TABLE_2T : {
       required : true,
       enabled  : true
     },
   TABLE_3 : {
       required : true,
       enabled  : true
     },
   TABLE_4 : {
       required : false,
       enabled  : true
     },
   TITLE_1 : {
       required : true,
       enabled  : true
     },
   TITLE_2 : {
       required : true,
       enabled  : true
     },
   VIDEO_1 : {
       required : true,
       enabled  : true
     },
   VIDEO_2 : {
       required : true,
       enabled  : true
     },
   VIDEO_3 : {
       required : true,
       enabled  : true
     },
   VIDEO_4 : {
       required : true,
       enabled  : true
     },
   VIDEO_5 : {
       required : true,
       enabled  : true
     },
   WIDGET_1 : {
       required : true,
       enabled  : true
     },
   WIDGET_2 : {
       required : true,
       enabled  : true
     },
   WIDGET_3 : {
       required : true,
       enabled  : true
     },
   WIDGET_4 : {
       required : true,
       enabled  : true
     },
   WIDGET_5 : {
       required : true,
       enabled  : true
     },
   WIDGET_6 : {
       required : true,
       enabled  : true
     },
   WIDGET_7 : {
       required : true,
       enabled  : true
     },
   WIDGET_8 : {
       required : true,
       enabled  : true
     },
   WIDGET_9 : {
       required : true,
       enabled  : true
     },
   WIDGET_10 : {
       required : true,
       enabled  : true
     },
   WIDGET_11 : {
       required : true,
       enabled  : true
     }
  }  
});


/**
 * Copyright 2011-2013 OpenAjax Alliance
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* --------------------------------------------------------------------------- */
/* OpenAjax Alliance (OAA) Ruleset for WCAG 2.0 (Beta)           */
/* --------------------------------------------------------------------------- */
   

OpenAjax.a11y.all_rulesets.addRuleset('WCAG20', {

  title : {
    'default' : "ARIA Strict",
    'en-us'   : "ARIA Strict"
  },   
  
  description : {
    'default' : "ARIA strict ruleset is based on best practice design patterns to improve accessibility and usability with the features available in HTML and ARIA specifications to meet WCAG 2.0 success criteria.",
    'en-us'   : "ARIA strict ruleset is based on best practice design patterns to improve accessibility and usability with the features available in HTML and ARIA specifications to meet WCAG 2.0 success criteria."  
  },
  
  author : {
    name : "OpenAjax Accessibility Working Group",
    url  : "http://www.openajax.org/member/wiki/Accessibility"
  }, 
  
  ruleset_id    : "ARIA_STRICT",  
  version       : "0.7 Beta",
  last_updated  : "2012-01-13",

  // Assignement of rules to WCAG 2.0 requirements

  // Assignement of rules to WCAG 2.0 requirements

  rule_mappings : {
   AUDIO_1 : {
       required : true,
       enabled  : true
     },
   COLOR_1 : {
       required : true,
       enabled  : true
     },
   CONTROL_1 : {
       required : true,
       enabled  : true
     },
   CONTROL_2 : {
       required : true,
       enabled  : true
     },
   CONTROL_3 : {
       required : true,
       enabled  : true
     },
   CONTROL_4 : {
       required : true,
       enabled  : true
     },
   CONTROL_5 : {
       required : true,
       enabled  : true
     },
   CONTROL_6 : {
       required : true,
       enabled  : true
     },
   CONTROL_7 : {
       required : true,
       enabled  : true
     },
   CONTROL_8 : {
       required : true,
       enabled  : true
     },
   CONTROL_9 : {
       required : true,
       enabled  : true
     },
   CONTROL_10 : {
       required : true,
       enabled  : true
     },
   CONTROL_11 : {
       required : true,
       enabled  : true
     },
   CONTROL_12 : {
       required : true,
       enabled  : true
     },
   FOCUS_1 : {
       required : true,
       enabled  : true
     },
   FOCUS_2 : {
       required : true,
       enabled  : true
     },
   FOCUS_3 : {
       required : true,
       enabled  : true
     },
   FOCUS_4 : {
       required : true,
       enabled  : true
     },
   HEADING_1 : {
       required : false,
       enabled  : true
     },
   HEADING_2 : {
       required : false,
       enabled  : true
     },
   HEADING_3 : {
       required : true,
       enabled  : true
     },
   HEADING_4 : {
       required : true,
       enabled  : true
     },
   HEADING_5 : {
       required : true,
       enabled  : true
     },
   IMAGE_1 : {
       required : true,
       enabled  : true
     },
   IMAGE_2 : {
       required : true,
       enabled  : true
     },
   IMAGE_3 : {
       required : true,
       enabled  : true
     },
   IMAGE_4_EN : {
       required : false,
       enabled  : true
     },
   IMAGE_5 : {
       required : false,
       enabled  : true
     },
   IMAGE_6 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_1 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_2 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_3 : {
       required : true,
       enabled  : true
     },
   KEYBOARD_4 : {
       required : true,
       enabled  : true
     },
   LANDMARK_1 : {
       required : true,
       enabled  : true
     },
   LANDMARK_2 : {
       required : true,
       enabled  : true
     },
   LANDMARK_6 : {
       required : true,
       enabled  : true
     },
   LAYOUT_1 : {
       required : true,
       enabled  : true
     },
   LAYOUT_2 : {
       required : false,
       enabled  : true
     },
   LAYOUT_3 : {
       required : true,
       enabled  : true
     },
   LINK_1 : {
       required : true,
       enabled  : true
     },
   LINK_2 : {
       required : true,
       enabled  : true
     },
   LINK_3 : {
       required : false,
       enabled  : true
     },
   LINK_4 : {
       required : true,
       enabled  : true
     },
   TABLE_1 : {
       required : true,
       enabled  : true
     },
   TABLE_2S : {
       required : false,
       enabled  : true
     },
   TABLE_2M : {
       required : true,
       enabled  : true
     },
   TABLE_3 : {
       required : true,
       enabled  : true
     },
   TABLE_4 : {
       required : false,
       enabled  : true
     },
   TITLE_1 : {
       required : true,
       enabled  : true
     },
   TITLE_2 : {
       required : true,
       enabled  : true
     },
   VIDEO_1 : {
       required : true,
       enabled  : true
     },
   VIDEO_2 : {
       required : true,
       enabled  : true
     },
   VIDEO_3 : {
       required : true,
       enabled  : true
     },
   VIDEO_4 : {
       required : true,
       enabled  : true
     },
   VIDEO_5 : {
       required : true,
       enabled  : true
     },
   WIDGET_1 : {
       required : true,
       enabled  : true
     },
   WIDGET_2 : {
       required : true,
       enabled  : true
     },
   WIDGET_3 : {
       required : true,
       enabled  : true
     },
   WIDGET_4 : {
       required : true,
       enabled  : true
     },
   WIDGET_5 : {
       required : true,
       enabled  : true
     },
   WIDGET_6 : {
       required : true,
       enabled  : true
     },
   WIDGET_7 : {
       required : true,
       enabled  : true
     },
   WIDGET_8 : {
       required : true,
       enabled  : true
     },
   WIDGET_9 : {
       required : true,
       enabled  : true
     },
   WIDGET_10 : {
       required : true,
       enabled  : true
     },
   WIDGET_11 : {
       required : true,
       enabled  : true
     }
  } 
});

return OpenAjax;

});
